C++进阶:coroutine 协程
1. 总述
协程的本质是一个可以暂停和恢复执行的函数,只要函数体出现了【co_await】、【co_yield】、【co_return】这三个关键字之一,则这个函数将自动变成协程。
而C++20引入的协程是一个无栈协程,其内存分配(协程帧)时机是在协程首次挂起时,注意不是函数调用时,内存默认在堆上分配,是否需要手动释放,依赖于co_return的返回值,详细内容会在下章节介绍。协程帧一般包括如下内容:
┌─────────────────┐
│ 协程帧头部 │ ← 编译器内部信息
├─────────────────┤
│ promise对象 │
├─────────────────┤
│ 参数副本 │ ← 函数参数按值/引用存储
├─────────────────┤
│ 局部变量 │ ← 跨挂起点的局部变量
├─────────────────┤
│ 挂起点信息 │ ← 恢复位置、寄存器保存等
├─────────────────┤
│ 激活记录 │ ← 嵌套调用信息(如适用)
└─────────────────┘C++中对协程函数的返回值类型做了限制,返回值类型必须要包含一个名称为【promise_type】的内嵌类型,其主要用于控制协程的行为。
相对于线程,C++协程的优势有很多,主要包括:
- 用同步风格写异步代码,代码可读性更好
- 协程创建更快,内存占用更低(不涉及栈空间分配),而且因为仅仅是用户层的调度,避免了线程上下文切换开销,性能也更优
2. 协程关键字/函数说明
C++协程中用到了多种关键字,这里面先做一个简单介绍,后续结合代码再做进一步理解:
1. co_await
该关键字的作用是将协程函数异步挂起,等待条件满足后再继续执行,语法为:
auto result = co_await expr; //expr必须为Awaitable类型对象Awaitable类型至少要包含如下三个成员函数,以便于协程自动化调用,co_wait关键字执行时,会立刻调用await_ready()函数,并根据其结果,进一步确认是否需要调用await_suspend().
当通过协程句柄(std::coroutine_handle<>)调用其resume()函数时,Awaitable类的await_resume()将被调用。
struct MyAwaitable {// 1. 检查是否需要挂起,// true:无需挂起,同普通函数,false:自动调用await_suspend函数bool await_ready();// 2. 挂起时的处理逻辑void await_suspend(std::coroutine_handle<> handle);// 3. 恢复时获取结果auto await_resume();
};2. co_yield
co_yield 是 C++ 协程中用于生成值序列并挂起协程的关键字。它将协程转变为生成器(Generator),能够按需产生值序列,在每次产生值后自动挂起,等待下一次请求。且若要使用该关键字,则pomise_type类中必须包含yield_value函数的实现。
使用语法为:
// promise_type函数必须实现yield_value()
co_yield expr;3. co_return
co_return是 C++ 协程中用于结束协程执行并返回最终结果的关键字。它标志着协程的完成,负责清理资源、设置返回值,并将协程置于最终状态。
co_return关键字执行时,会自动化调用promise_type类型中的return_value()或return_void()函数,以获取最终的结果。并在其执行完毕后,自动化执行promise_type对象的final_suspend()函数。
常见语法为:
// 返回一个值
co_return expression;// 无返回值(void 协程)
co_return;
4. std::suspend_never
主要用于修饰如下三个函数,表征该函数执行完成后无需挂起,要立刻继续执行:
- initial_suspend():协程立即开始执行,不初始挂起
- final_suspend():协程完成后自动销毁
- yield_value():co_yield 后不挂起,继续执行
5. std::suspend_always
std::suspend_always 是一个总是挂起的 Awaitable 类型,它告诉协程必须挂起,等待后续恢复
- initial_suspend():协程创建后立即挂起,等待手动恢复
- final_suspend():协程完成后保持挂起,内存需要手动清理
- yield_value():co_yield 后挂起,等待下一次请求
3. 示例代码及流程解析
下面将结合代码介绍下,协程的整体流程,代码如下:
class IntReader {
public:bool await_ready() {return false;}void await_suspend (std::coroutine_handle<> h) {std::thread([this, h]() {sleep(1000);value_ = 1;h.resume();})thread.detach();}await_resume() {return value_;}
private:int value_{0};
};class Task {class pomise_type{public:pomise_type() : value_(std::make_shared<int>()){}Task get_return_object() {return Task{std::coroutine_handle<promise_type>::from_promise(*this),value_};}void return_value (int value) {*value_ = value;}std::suspend_never initial_suspend() { return {};}std::suspend_never final_suspend() noexcept { return {}; }void unhandled_exception(){}private:std::shared_ptr<int> value_;};
public:Task(std::coroutine_handle<promise_type> handle,const std::shared_ptr<int> & value) : handle_(handle),value_(value) {}int GetValue() const {return *value;}
private:std::shared_ptr<int> value_;std::coroutine_handle<promise_type> handle_;
};Task GetInt() {IntReader reader1;int total = co_wait reader1;IntReader reader2;total += co_wait reader2;IntReader reader3;total += co_wait reader3;co_return total;
}int main() {auto task = GetInt();std::cout << task.GetValue() << std::endl;return 0;
}首先,这里需要强调的是,在调用协程函数时,函数的返回值,即Task对象是有系统自动化调用promise_type对象的get_return_object()函数自动生成的,而非我们主动声明的,这和我们之前的使用方式,差异很大。

