目录
[Awaitable 对象](#Awaitable 对象)
前言
最近在学习协程,学完后计划整理一份复习资料,帮助自己更好的掌握知识点,应对面试。
什么是协程
一种可以暂停执行并恢复执行的函数。与普通函数的 "执行完才退出" 不同,协程可以在执行中途挂起,保存当前所有上下文,之后从挂起处恢复。
普通函数像坐飞机,从起点直飞终点,中间不能停。协程像高铁,可以在沿途各个站点上下客、停靠,然后继续行驶。
当⼀个函数包含 co_await/co_yield/co_return 任意⼀个关键字时,它就是协程。
- co_await expression 用于暂停执行直到恢复
- co_yield expression 用于暂停执行并返回⼀个值
- co_return expression 用于完成执行并返回⼀个值
C++20 的协程是无栈协程,这意味着暂停时只保存协程帧(程序计数器、局部对象、Promise对象
等),而不是整个调用栈。这使得它们非常轻量,但意味着在协程内暂停时,其调用者也会被暂停。
协程的三要素
Promise对象
协程的 "管家"。负责定义协程的行为,比如如何返回值、如何抛出异常、何时开始、结束。
Promise 类型必须提供以下核心接口,用来控制协程的行为。
get_return_object()
- 作用:创建并返回给调用者协程的返回值。这是在协程函数体执行之前被调用的。
- 返回值类型:协程的返回类型(例如外层的Task、Generator)。
initial_suspend()
- 作用:决定协程在开始执行函数体之前是否应该先挂起。
- 返回值:⼀个满足Awaitable 概念的对象(通常是 std::suspend_always 或 std::suspend_never )。
- std::suspend_always{} : 总是挂起。这意味着协程⼀创建就是惰性的,不会自动开始执行,需要手动 resume() ,这是"懒汉式"协程的常见选择。
- std::suspend_never{} : 从不挂起。协程创建后会立即开始执行,直到遇到第⼀个挂起点。这是"饿汉式"协程的常见选择。
final_suspend()
- 作用:决定协程在执行完函数体(或通过 co_return 返回)后,在彻底销毁自身之前是否应该挂起。
- 返回值:同样是⼀个 Awaitable 概念对象。(通常是 std::suspend_always 或 std::suspend_never )。
- 如果返回 std::suspend_always ,你必须在协程外部某处手动调用.destroy() 来销毁协程帧,否则会发生内存泄漏。如果返回 std::suspend_never ,协程会在返回后自动清理自己。
return_void() 和 return_value(...)
- 作用:处理 co_return 语句。
- Promise 类型必须实现这两个的其中之⼀,但不能同时实现。
- void return_void() : 用于处理 co_return; (无返回值)。
- void return_value(type value) : 用于处理 co_return value; (有返回值)。 value 的类型由使用者指定,通常使用者会把它存储到 Promise 的⼀个成员变量中,以便外部获取。
yield_value(...)
- 作用:处理 co_yield 语句。 co_yield expression 本质上被编译器翻译为 co_await promise.yield_value(expression) 。
- 返回值:⼀个 Awaitable 概念对象,用于决定在产出值之后是否要挂起协程(通常返回 std::suspend_always{} 来挂起,让调⽤者有机会处理产出的值)。
unhandled_exception()
- 作用:当协程体内发生异常,并且该异常未被协程体内的 try/catch 块捕获时,这个异常会被自动传递给 unhandled_exception() 方法。
- unhandled_exception() : Promise 类型的核心异常处理函数,协程内任何未捕获的异常都会调用此函数,std::current_exception() : 捕获当前异常并转换为 std::exception_ptr,std::rethrow_exception() : 重新抛出存储的异常。
- RAII 模式在协程中依然有效,即使协程抛出异常,栈上的对象也会正确析构,协程帧本⾝的内存由 coroutine_handle 管理。
协程句柄
std::coroutine_handle<>。协程的 "遥控器"。用于恢复协程执行、销毁协程、检查是否挂起。
协程句柄的核心操作 API 主要有以下几个:
from_promise()/promise()
- static coroutine_handle from_promise(Promise& p ); ,用于使用⼀个 Promise 对象创建⼀个指向 Promise 对象的句柄,通常在 Promise 类型的 get_return_object 成员函数中使用。
- Promise& promise() const; 方便我们使用句柄可以获取管理的 Promise 对象。
resume()/operator()
- 作用:让⼀个处于挂起状态的协程继续执行。
- 行为:调用后,协程从上次挂起的地方开始执行,直到遇到下⼀个挂起点、结束或抛出异常。
- 注意:不能在非挂起状态的协程上调用(如已运行完成或尚未开始的协程,行为未定义)。
done()
- 作用:查询协程是否已执行完成(即是否已经到达函数体末尾或执行了 co_return )。
- 返回值: true : 协程已执行完成, false : 协程仍在运行或处于挂起状态。
operator bool()
- 作用:查询是否是⼀个有效的协程句柄。
- 返回值: true : 指向⼀个有效的协程, false : 协程句柄内存指向⼀个 nullptr 指针。
destroy()
- 作用:显式销毁协程帧,释放其内存。
- 何时调用:通常在协程返回类型的析构中调用,只有当协程在 final_suspend() 返回std::suspend_always 挂起时,才需要(且必须)手动调用.destroy() 。如果 final_suspend() 返回 std::suspend_never ,协程会自动销毁自己,再调用.destroy() 就是未定义行为。
operator coroutine_handle<>()
- 作用:该转换函数将 std::coroutine_handle<Promise> 值转换为包含相同底层 address 的 std::coroutine_handle<> 值。
- 何时调用: void await_suspend(std::coroutine_handle<> h) 中作为形参接收各协程的句柄。
Awaitable 对象
协程的 "等待条件"。定义协程何时挂起、何时恢复(例如等待 IO 完成、等待一个结果)。
⼀个类型要成为 Awaitable,它必须实现三个特定的成员函数await_ready/await_suspend/await_resume ,或者通过 operator co_await 重载返回⼀个实现了这些函数的对象。
bool await_ready() const
- 目的:性能优化。在尝试暂停之前,检查异步操作是否已经完成。
- 返回值: true :表示结果已就绪,无需暂停协程。编译器将跳过 await_suspend 和暂停步骤,直接调用 await_resume 并继续执行。 false :表示结果未就绪,需要暂停协程,继续执行await_suspend 逻辑。
- 最佳实践通常实现为 noexcept 。对于立即完成的操作(如缓存命中),返回 true 可以避免不必要的暂停开销。
type await_suspend( std::coroutine_handle<>) const
- 这是最强大也是最复杂的部分,它在协程即将暂停但尚未暂停时被调用。
- 目的:安排异步操作的回调,它的职责是获取当前协程的句柄 handle ,并将其传递给某个异步操作,以便在操作完成后恢复协程。
- 参数: std::coroutine_handle<> ,代表当前正在执行的协程,你可以保存它、传递它
- 返回值和行为:返回值类型至关重要,它决定了控制流的走向
- 返回值行为 void ,默认行为,协程暂停,控制权返回到当前协程调用者。这是最常见的情 况。
- 返回类型为 bool , true 表示协程已挂起到后台(可能在另⼀个线程恢复); false 表示不挂起,立即恢复,用于在最后关头发现需要等待的操作已完成的情况。
- 返回类型为 std::coroutine_handle<> ,另⼀个协程句柄 handle 协程暂停,然后立即恢复 handle 所代表的协程,这实现了对称转移,是无栈协程链式调用的关键。
- 最佳实践: await_suspend 通常实现为 noexcept ,在这里通常会调用某个异步 API,并将 handle 作为其完成回调,绝对不要在此函数内阻塞。
type await_resume() const
- 目的:获取结果或处理错误。在协程恢复后或 await_ready 返回 true 时,此函数被调用以产生co_await 表达式的结果。
- 返回值:可以是 void 或任何其他类型,这个返回值就是整个 co_await 表达式的结果。
- 如果异步操作产生⼀个值, await_resume() 返回这个值。
- 如果异步操作可能失败, await_resume() 可以检查错误码并选择返回⼀个值或抛出⼀个异 常。
- 最佳实践:可以抛出异常,不抛异常时标记为 noexcept 。
C++20 在标准库中提供了两个最简单的 Awaitable 类型。
std::suspend_always
- await_ready() : 返回 false ,总是暂停。
- await_suspend(...) : 无操作,直接返回 void。
- await_resume() : 返回 void。
- 用途:用于 initial_suspend() 和 final_suspend() ,表示请在此处暂停。
std::suspend_never
- await_ready() : 返回 true ,从不暂停。
- await_suspend(...) : 永远不会被调用。
- await_resume() : 返回 void 。
- 用途:用于 initial_suspend() ,表示请不要暂停,立即开始执行协程体。
样例一:生成器
#include <coroutine>
#include <exception>
#include <iostream>
// 1. 协程的返回类型
struct Generator {
// 2. 核心:promise_type
struct promise_type {
int current_value; // 用于存储产出的值
// 2a. 创建返回值对象
Generator get_return_object() {
return Generator{
std::coroutine_handle<promise_type>::from_promise(*this) };
}
// 2b. 初始挂起:选择挂起,让协程惰性执行
std::suspend_always initial_suspend() { return {}; }
// 2c. 最终挂起:选择挂起,我们需要手动销毁
std::suspend_always final_suspend() noexcept { return {}; }
// 2d. 处理 co_yield
std::suspend_always yield_value(int value) {
current_value = value;
return {}; // 产出后总是挂起
}
// 2e. 处理 co_return; (无值返回)
void return_void() {}
// 2f. 处理异常
void unhandled_exception() { std::terminate(); }
};
// 3. Generator 类本身的成员
// 协程句柄,用于从外部控制协程
std::coroutine_handle<promise_type> handle;
// 构造函数和析构函数
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() {
if (handle) handle.destroy(); // 负责销毁协程帧
}
// 4. 提供给外部的API
// 获取当前值
int value() const {
return handle.promise().current_value;
}
// 恢复执行直到下⼀个co_yield或结束
bool next() {
if (!handle.done()) {
handle.resume();
}
return !handle.done();
}
};
// 使用这个生成器的协程函数
Generator range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i; // 这会调用 promise.yield_value(i)
}
// 协程结束,调用 promise.return_void()
}
int main() {
auto gen = range(1, 5); // 创建协程,initial_suspend挂起,此时协程未执⾏
while (gen.next()) { // 恢复协程,协程执⾏到co_yield处挂起
std::cout << gen.value() << " "; // 从promise中获取产出的值
}
// 输出: 1 2 3 4
}
逻辑拆解:
-
协程创建阶段(触发: auto gen = range(1,5) ):调用 range 协程函数时,编译器自动创建协程帧(存储局部变量 i 、参数 start=1/end=5 、 promise_type 实例),并通过 promise 衔接 Generator。
-
初始挂起状态(触发: initial_suspend() ):协程创建后不立即执行,而是先挂起( suspend_always 表示 "总是挂起"),实现惰性执行(按需启动)。
-
恢复执行阶段(触发: gen.next() ):外部通过 Generator 的 next() 接口,用 handle 唤醒协程,执行到下⼀个 co_yield 或协程结束。
-
产出值挂起状态(触发: co_yield ): co_yield 是协程 "产出值" 的关键字,本质是调用promise.yield_value() ,并挂起协程,让外部获取产出值。
-
协程结束状态(触发: for 循环退出):range 的 for 循环执行完毕( i=5 >= end=5 ),协程无更多代码可执行,进入结束流程。
-
最终挂起状态(触发: final_suspend() ):协程结束后,通过 final_suspend() 挂起,等待外部手动销毁(避免协程帧提前释放)。
-
协程销毁阶段(触发: gen 析构) :main 函数结束, Generator 对象 gen 出作用域,析构函数释放协程帧内存。
下面是支持迭代器的协程返回对象:
#include <coroutine>
#include <iostream>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
};
struct iterator {
std::coroutine_handle<promise_type> handle;
iterator(std::coroutine_handle<promise_type> h = nullptr) : handle(h)
{}
iterator& operator++() {
if (handle && !handle.done()) {
handle.resume();
}
return *this;
}
T operator*() const {
return handle.promise().current_value;
}
bool operator!=(const iterator& other) const {
return !handle.done();
}
};
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
iterator begin() {
if (handle)
handle.resume();
return iterator{ handle };
}
iterator end() { return iterator{}; }
private:
std::coroutine_handle<promise_type> handle;
};
// 使用示例
Generator<int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i; // 产生值并挂起
}
}
Generator<int> fibonacci(int limit) {
int a = 0, b = 1;
while (a <= limit) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
int main() {
std::cout << "Range 1-5: ";
for (auto i : range(1, 6)) {
std::cout << i << " ";
}
std::cout << std::endl;
std::cout << "Fibonacci below 50: ";
for (auto num : fibonacci(50)) {
std::cout << num << " ";
}
std::cout << std::endl;
}
样例二:异步线程恢复
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
// 增强的可等待对象,添加更多日志
struct AsyncOperation {
int value;
bool ready = false;
const char* name;
AsyncOperation(int v, bool r, const char* n = "")
: value(v), ready(r), name(n) {
std::cout << "[" << name << "] AsyncOperation 构造: value=" << value<<", ready=" << ready << " (线程: " << std::this_thread::get_id() << ")" <<std::endl;
}
bool await_ready() const noexcept {
std::cout << "[" << name << "] await_ready() 调用, 返回: " << ready<< "(线程: " << std::this_thread::get_id() << ")" << std::endl;
return ready;
}
void await_suspend(std::coroutine_handle<> handle) noexcept {
std::cout << "[" << name << "] await_suspend() 调用, 协程句柄: " <<handle.address()<< " (线程: " << std::this_thread::get_id() << ")" << std::endl;
// 启动异步操作
std::thread([this, handle, name = this->name]() {
std::cout << "[" << name << "] 异步线程开始 (线程: "<<std::this_thread::get_id() << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
this->ready = true;
std::cout << "[" << name << "] 异步操作完成, 准备恢复协程" << " (线程:" << std::this_thread::get_id() << ")"<< std::endl;
// 恢复协程执行
handle.resume();
std::cout << "[" << name << "] 异步线程结束" << " (线程: " <<
std::this_thread::get_id() << ")" << std::endl;
}).detach();
std::cout << "[" << name << "] await_suspend() 返回, 协程即将挂起" << "(线程: " << std::this_thread::get_id() << ")" << std::endl;
}
int await_resume() noexcept {
std::cout << "[" << name << "] await_resume() 调用, 返回结果: " <<value<< " (线程: " << std::this_thread::get_id() << ")" << std::endl;
return value;
}
};
struct Task {
struct promise_type {
int current_value;
Task get_return_object() {
std::cout << "promise_type::get_return_object() 调用" << std::endl;
return Task{std::coroutine_handle<promise_type>::from_promise(*this) };
}
std::suspend_never initial_suspend() noexcept {
std::cout << "promise_type::initial_suspend() 调用 - 立即开始执行"<< std::endl;
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "promise_type::final_suspend() 调用 - 协程结束" <<std::endl;
return {};
}
void unhandled_exception() {
std::cout << "promise_type::unhandled_exception() 调用" <<std::endl;
}
void return_void() {
std::cout << "promise_type::return_void() 调用 - 协程正常返回" <<std::endl;
}
};
std::coroutine_handle<promise_type> handle;
Task(std::coroutine_handle<promise_type> h) : handle(h) {
std::cout << "Task 对象构造, 句柄: " << handle.address() << std::endl;
}
~Task() {
if (handle) {
std::cout << "Task 析构, 销毁协程句柄" << std::endl;
handle.destroy();
}
}
};
Task detailed_async_example() {
std::cout << "=== 协程函数开始执行 ===" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
std::cout << "\n--- 第一次 co_await ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
int result1 = co_await AsyncOperation{ 100, false, "操作1" };
std::cout << "第一次 co_await 后继续执行, 结果: " << result1 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
std::cout << "\n--- 第二次 co_await ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
int result2 = co_await AsyncOperation{ 200, false, "操作2" };
std::cout << "第二次 co_await 后继续执行, 结果: " << result2 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
std::cout << "\n--- 第三次 co_await (就绪状态) ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
int result3 = co_await AsyncOperation{ 300, true, "操作3" };
std::cout << "第三次 co_await 后继续执行, 结果: " << result3 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
std::cout << "\n=== 协程函数执行完成 ===" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
}
int main() {
std::cout << "主函数开始 (线程: " << std::this_thread::get_id() << ")" <<std::endl;
{
std::cout << "\n********** 创建协程任务 **********" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
auto task = detailed_async_example();
std::cout << "协程任务已创建,控制权返回主函数" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
// 给异步操作时间完成
std::cout << "\n主线程等待 3 秒..." << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "主线程等待结束" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
} // task 析构
std::cout << "\n主函数结束" << " (线程: " << std::this_thread::get_id() <<")" << std::endl;
return 0;
}
样例三:echo服务器
#include <iostream>
#include <coroutine>
#include <thread>
#include <vector>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <memory>
#include <array>
// 将文件描述符设置为非阻塞模式
static void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0); // 获取当前文件状态标志
if (flags == -1) {
perror("fcntl F_GETFL");
return;
}
// 添加非阻塞标志
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL O_NONBLOCK");
}
}
// I/O 等待器:用于协程等待 epoll 事件
struct IoAwaiter {
int fd; // 要等待的文件描述符
int events; // 要等待的事件类型:EPOLLIN(可读) 或 EPOLLOUT(可写)
epoll_event ev{}; // epoll 事件结构
// 静态成员,保存 epoll 实例的文件描述符
static inline int epfd = -1;
// 协程等待开始前调用
// 返回 false 表示需要挂起等待
bool await_ready() const noexcept {
return false;
}
// 挂起协程,并将事件注册到 epoll
void await_suspend(std::coroutine_handle<> h) noexcept {
// 将协程句柄存到 epoll_event 的 data.ptr 中
ev.data.ptr = h.address();
// 设置事件类型,并使用边缘触发(EPOLLET)
ev.events = events | EPOLLET;
// 将 fd 和事件注册到 epoll
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}
// 协程恢复后调用
// 从 epoll 中移除该事件
void await_resume() noexcept {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
}
};
// 等待 accept 事件的协程等待器
IoAwaiter co_accept(int fd) {
return IoAwaiter{fd, EPOLLIN};
}
// 等待读事件的协程等待器
IoAwaiter co_read(int fd) {
return IoAwaiter{fd, EPOLLIN};
}
// 等待写事件的协程等待器
IoAwaiter co_write(int fd) {
return IoAwaiter{fd, EPOLLOUT};
}
// 协程任务类型
struct Task {
struct promise_type {
// 返回协程对象
Task get_return_object() { return {}; }
// 初始不挂起
std::suspend_never initial_suspend() noexcept { return {}; }
// 结束不挂起
std::suspend_never final_suspend() noexcept { return {}; }
// 协程返回 void
void return_void() {}
// 异常处理
void unhandled_exception() { std::terminate(); }
};
};
// 每个客户端连接的处理协程
Task echo_session(int sock) {
try {
std::array<char, 4096> buf; // 缓冲区
for (;;) {
// 等待 socket 可读
co_await co_read(sock);
// 读取数据
ssize_t n = read(sock, buf.data(), buf.size());
if (n <= 0) {
// 连接关闭或出错
if (n < 0) perror("read");
break;
}
// 等待 socket 可写
co_await co_write(sock);
// 回写数据
write(sock, buf.data(), n);
}
} catch (...) {
std::cerr << "Session error\n";
}
close(sock); // 关闭连接
}
// 监听协程:接受新连接并创建会话协程
Task listener(int port) {
// 创建监听 socket
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (s < 0) { perror("socket"); co_return; }
// 设置 socket 选项:允许地址重用
int opt = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址和端口
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
addr.sin_port = htons(port); // 端口号
if (bind(s, (sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
co_return;
}
// 开始监听
if (listen(s, SOMAXCONN) < 0) {
perror("listen");
co_return;
}
std::cout << "Listening on port " << port << std::endl;
// 循环接受新连接
for (;;) {
// 等待监听 socket 可读(有新连接)
co_await co_accept(s);
// 接受连接
sockaddr_in peer{};
socklen_t len = sizeof(peer);
int client = accept4(s, (sockaddr*)&peer, &len, SOCK_NONBLOCK);
if (client < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("accept");
}
continue;
}
std::cout << "New connection from " << inet_ntoa(peer.sin_addr) <<
std::endl;
// 为新连接创建会话协程
echo_session(client);
}
}
int main() {
// 创建 epoll 实例
IoAwaiter::epfd = epoll_create1(0);
if (IoAwaiter::epfd < 0) {
perror("epoll_create1");
return 1;
}
// 启动监听协程
listener(9000);
// 事件循环
std::vector<epoll_event> events(64); // 事件数组
for (;;) {
// 等待事件发生
int nfds = epoll_wait(IoAwaiter::epfd, events.data(), events.size(),
-1);
if (nfds < 0) {
perror("epoll_wait");
break;
}
// 处理每个就绪事件
for (int i = 0; i < nfds; ++i) {
// 从事件中取出协程句柄并恢复执行
auto h = std::coroutine_handle<>::from_address(events[i].data.ptr);
h.resume();
}
}
}
面试题:
C++20 协程有什么缺陷?为什么 C++23 引入了协程栈大小优化?
C++20 协程的栈是无栈(stackless)的,意味着协程只能挂起在最后一个co_await/co_yield处,不支持递归协程(递归调用自身会导致栈溢出或行为未定义)。C++23 引入了有栈协程(stackful),支持更灵活的挂起和恢复,解决了递归等场景。
协程和线程的区别?
线程开销大,拥有 TCB 控制块、独立栈、TID 等内核级结构,是内核调度的最小单位。协程是用户态的轻量级执行单元,调度开销极小,由程序自身控制。仅在用户态通过堆上的 "协程帧" 保存状态,所有执行都复用线程的内核结构,完全依赖线程存在。千级 / 万级协程并发是可行的,而线程则不行。协程通常运行在线程池中,利用线程来执行。
协程的内存管理需要注意什么?
协程帧分配在堆上,必须确保其生命周期正确管理。忘记co_await一个协程任务,可能导致协程帧在任务完成前就被销毁,造成未定义行为。使用 RAII自动管理句柄销毁是良好实践。
说说std::suspend_always和std::suspend_never的区别和用途
- std::suspend_never: await_ready() 返回True,协程永不挂起,立即执行。常用于initial_suspend()和final_suspend(),表示协程一开始就运行,或结束时立即清理。
- std::suspend_always: await_ready() 返回false,协程总是挂起。需要手动调用 resume() 才能继续。
协程的应用场景
- 异步 IO:传统异步 IO 依赖回调函数,代码嵌套深、可读性差、错误处理复杂;用线程池 + 同步 IO 则线程开销大(线程阻塞等待 IO 时,内核态切换 + 栈内存占用)。协程用co_await替代回调,代码逻辑和同步 IO 一致,但协程挂起时无内核态切换(用户态暂停),且协程栈(堆分配)仅几十字节,远小于线程栈(MB 级)。
- 耗时任务的 "轻量级并发":CPU 密集型 + IO 密集型混合任务(如 "请求接口→解析数据→计算结果"),用线程池会因线程数过多导致调度开销大;用单线程则串行执行效率低。协程可在单线程内并发执行多个任务(IO 等待时挂起,CPU 计算时恢复),既利用 CPU,又不浪费线程资源。
协程的不适用场景
- 纯 CPU 密集型任务:协程的优势是 "挂起 / 恢复",但纯 CPU 任务(如矩阵运算、加密解密)无挂起点,协程无法发挥作用,且协程的堆分配 / 状态机转换会带来微小开销,不如直接用线程池 + 多线程并行(充分利用多核)。
- 简单的短任务:如 "计算两数之和""字符串拼接",协程的初始化(堆分配协程帧)开销远大于任务本身,不如普通函数高效。
- 需要实时性的场景:协程的调度依赖用户态逻辑(无内核抢占),若一个协程长期占用 CPU,会阻塞同线程的其他协程,无法满足硬实时(如工业控制、车载系统)要求,不如线程(内核抢占调度)可靠。