参考[cppreference](https://zh.cppreference.com/)
的解释如下:
A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously
从上面的解释可以简单解释下C++20
中协程的基本概念
C++20
的协程是无栈协程。#include
#include
struct HelloCoroutine {
struct HelloPromise {
HelloCoroutine get_return_object() {
std::cout << "==============call get_return_object==============" << std::endl;
return std::coroutine_handle::from_promise(*this);
}
std::suspend_never initial_suspend() {
std::cout << "==============call initial_suspend==============" << std::endl;
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "==============call final_suspend==============" << std::endl;
return {};
}
void unhandled_exception() {}
};
using promise_type = HelloPromise;
HelloCoroutine(std::coroutine_handle h) : handle(h) {}
std::coroutine_handle handle;
};
std::coroutine_handle<> coroutine_handle;
struct AWaitableObject
{
AWaitableObject() {}
bool await_ready() const {
std::cout << "==============call await_ready==============" << std::endl;
return false;
}
int await_resume() {
std::cout << "==============call await_resume==============" << std::endl;
return 0;
}
void await_suspend(std::coroutine_handle<> handle) {
std::cout << "==============call await_suspend==============" << std::endl;
coroutine_handle = handle;
}
};
HelloCoroutine hello() {
std::cout << "Hello " << std::endl;
co_await AWaitableObject{};
std::cout << "world!" << std::endl;
}
int main() {
std::cout << "start" << std::endl;
HelloCoroutine coro = hello();
std::cout << "calling resume" << std::endl;
coro.handle.resume();
std::cout << "destroy" << std::endl;
coro.handle.destroy();
return 0;
}
用gcc-11.1.0
版本编译并运行
root@DESKTOP-QA1H9TD:/mnt/d/Code/os# g++-11 coroutine.cpp -std=c++2a
root@DESKTOP-QA1H9TD:/mnt/d/Code/os# ./a.out
start
==============call get_return_object==============
==============call initial_suspend==============
Hello
==============call await_ready==============
==============call await_suspend==============
calling resume
==============call await_resume==============
world!
==============call final_suspend==============
destroy
root@DESKTOP-QA1H9TD:/mnt/d/Code/os#
观察运行结果发现,在使用了关键字co_await
后会返回到caller
, 在main
中使用resume()
后,回到co_await
的赋值等式中运行
Results CoroutineFunction(){
co_await AwaitatbleObject();
co_return {};
}
一个协程函数的形式如上所示,当函数体内出现了co_await, co_yield,co_return这三个关键字之后,就会被当成一个协程函数。并且,编译器要求返回值类型包含一个promise_type的结构以及需要实现必要的函数,以上一个例子中的HelloCoroutine
类型为例:
struct HelloCoroutine {
struct HelloPromise {
HelloCoroutine get_return_object() {
std::cout << "==============call get_return_object==============" << std::endl;
return std::coroutine_handle::from_promise(*this);
}
std::suspend_never initial_suspend() {
std::cout << "==============call initial_suspend==============" << std::endl;
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "==============call final_suspend==============" << std::endl;
return {};
}
void unhandled_exception() {}
};
promise_type放在协程函数返回值类型HelloCoroutine
中,用于控制协程的流程。观察打印结果并总结,它在运行过程中主要分为如下三个阶段:
初始化准备阶段
get_return_object()
函数创建返回值对象,在协程第一次返回时就会把这个对象返回给caller;initial_suspend()
函数,这个返回值有两个选择suspend_never/suspend_always,never表示继续运行,always表示协程挂起,同时把返回值对象返回,所以这个接口的语义是,协程创建后是否马上运行运行阶段
开始运行协程函数,如果出现异常会调用unhandled_exception()
去处理
例子中用到了co_await
关键字,这是一个一元操作符,操作的对象为awaitable
类型,就是实现await_ready(), await_resume(), await_suspend( ) 的类型,负责管理协程挂起时的行为,如例子所示的AWaitableObject。
struct AWaitableObject
{
AWaitableObject() {}
bool await_ready() const {
std::cout << "==============call await_ready==============" << std::endl;
return false;
}
int await_resume() {
std::cout << "==============call await_resume==============" << std::endl;
return 0;
}
void await_suspend(std::coroutine_handle<> handle) {
std::cout << "==============call await_suspend==============" << std::endl;
coroutine_handle = handle;
}
};
首先运行await_ready( )
函数,判断是否要挂起当前线程: 如果是false,则挂起; 如果是true,则表示不要挂起,然后会调用await_suspend()
,用于提供挂起前的处理,然后协程就被挂在这个点
然后一旦协程被恢复运行时,继续调用**await_resume()**在返回一个值到协程挂起点
co_await
除了显示使用之外,promise_type的接口中凡是返回了suspend_never/suspend_always
的地方,编译器都是通过co_await的方式调用这些函数的,suspend_never/suspend_always是awaitable
类型
struct suspend_always
{
bool await_ready() { return false; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
struct suspend_never
{
bool await_ready() { return true; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
如果遇到co_yield var
这样的表达式,表示想要挂起当前协程,返回一个值给caller, 编译器相当于调用了yield_value(var)
方法,我们可以此时将值设置到Result的相关变量中,编译器会继续根据函数的返回值判断是否为suspend_always判断要返回到caller点。改造原示例如下:
struct HelloCoroutine {
struct HelloPromise {
std::string_view value_;
HelloCoroutine get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
std::suspend_always yield_value(std::string_view value) {
value_ = value;
std::cout << value_ << std::endl;
return {};
}
};
using promise_type = HelloPromise;
HelloCoroutine(std::coroutine_handle h) : handle(h) {}
std::coroutine_handle handle;
};
HelloCoroutine hello() {
std::string_view s = "Hello ";
co_yield s;
std::cout << "world" << std::endl;
}
如果co_return
这样的表达式,想要结束协程返回一个对象,则会调用return_value()
这个函数,设置好要返回的相关值; 如果整个协程都没有出现co_return
,则会调用return_void()
struct HelloCoroutine {
struct HelloPromise {
HelloCoroutine get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() { return {}; }
void unhandled_exception() {}
void return_value(int value) {
std::cout << "got co_return value " << value << std::endl;
}
};
using promise_type = HelloPromise;
HelloCoroutine(std::coroutine_handle h) : handle(h) {}
std::coroutine_handle handle;
};
HelloCoroutine hello() {
std::cout << "Hello " << std::endl;
co_await std::suspend_always{};
std::cout << "world!" << std::endl;
co_return 42;
}
结束阶段
final_suspend()
判断协程已处理完毕释放前是否要挂起