协程简介
协程是一个能在执行过程中被挂起、随后又能恢复执行的函数。
c++20中使用的是无栈协程,对协程的定义为:一个函数如果包含co_await、co_yield和co_return三个关键字中的任意一个,则被称为协程。
co_await关键字
co_await
是一个一元操作符,后面跟一个表达式expr
,能挂起一个协程并将控制权返回给调用方。这里的expr
需要返回一个awaitable
对象,awaitable
对象内部定制了当前协程是否需要挂起等操作。
awaitable对象
定义一个awaitable
对象,主要需要实现三个函数:
- await_ready(): 在被
co_await
时是否已经准备好了,如果返回false
,那么co_await awaitable
就会立即暂停该协程,然后调用awaitable.await_suspend()
。如果返回true
,说明数据已经准备好了,那么就会继续执行该协程。
- await_suspend():协程被挂起,该函数就会被调用。该函数返回
void
或true
,协程挂起,将控制权交给调用者;如果返回false
,则会立即恢复该协程;如果返回其他协程句柄,则立即恢复那个协程。
- await_resume():当协程句柄的
resume()
函数被调用,则该函数被调用。该函数的返回值就是co_await awaitable
的返回值。
协程库中提供了两个awaitable
对象:
- suspend_always;表示
co_await expr
需要挂起,不产生值。await_suspend
和await_resume
都为空,await_ready
直接返回false
。
- suspend_never:表示
co_await expr
不需要挂起。await_ready
直接返回true
。
一个awaitable
对象的简单定义:
class MyAwaitable { public: MyAwaitable() = default;
bool await_ready() { std::cout << "await ready" << std::endl; return false; } void await_suspend(std::coroutine_handle<> handle) { std::cout << "await suspend" << std::endl; } void await_resume() { std::cout << "await resume" << std::endl; } };
|
协程对象
现在我们有个函数,函数内部包含co_await
,这就是一个协程,但如果想操作这个协程,我们仍需要定义一个协程对象,用来操作该协程和获取协程状态。
一个协程对象要和一个承诺对象(promise object)关联,承诺对象由协程对象操作,协程通过承诺对象提交结果或执行异常处理,协程对象由调用者操作。
承诺对象至少需要实现五个函数:
- get_return_object():获取协程对象。我们可以通过
std::coroutine_handle::from_promise()
获取关联的协程句柄。
- initial_suspend():该函数返回一个
awaitable
对象,协程在初始化时会co_await
它,对于惰性启动的协程,可以返回std::suspend_always
,对于立即启动的协程,可以返回std::suspend_never
。
- final_suspend():返回一个
awaitable
对象,如果返回suspend_never
,程序结束时不会挂起,会释放相关资源。如果返回suspend_always
,协程结束时挂起,调用者仍可以从协程对象中获取相关资源。注意,此时恢复该协程会发生未定义的行为。该函数必须是noexcept
的,不能引发异常。
- unhandled_exception():当协程因异常结束,调用该函数处理异常。
- reture_void()/reture_value():和
co_reture
关键字相关,当co_return;
不携带返回值,调用reture_void
,协程函数末尾会隐含一个co_return
;co_reture expr;
携带返回值,调用return_value
。
一个简单协程对象的定义:
class MyCoroutine { public: class MyPromise { public: MyCoroutine get_return_object() { std::cout << "get coroutine handle" << std::endl; return std::coroutine_handle<MyPromise>::from_promise(*this); } std::suspend_never initial_suspend() { std::cout << "initial suspend" << std::endl; return {}; } std::suspend_never final_suspend() noexcept { std::cout << "final suspend" << std::endl; return {}; } void unhandled_exception() {
} void return_void() { std::cout << "return void" << std::endl; } };
using promise_type = MyPromise; MyCoroutine(std::coroutine_handle<MyPromise> handle) : _handle(handle) {}
void resume() { _handle.resume(); }
private: std::coroutine_handle<MyPromise> _handle; };
|
一个协程的运行过程
我们利用上面实现的awaitable
对象和协程对象来实现一个完整的协程,观察协程的运行过程。
#include <iostream> #include <coroutine>
class MyCoroutine { public: class MyPromise { public: MyCoroutine get_return_object() { std::cout << "get coroutine handle" << std::endl; return std::coroutine_handle<MyPromise>::from_promise(*this); } std::suspend_never initial_suspend() { std::cout << "initial suspend" << std::endl; return {}; } std::suspend_never final_suspend() noexcept { std::cout << "final suspend" << std::endl; return {}; } void unhandled_exception() {
} void return_void() { std::cout << "return void" << std::endl; } };
using promise_type = MyPromise; MyCoroutine(std::coroutine_handle<MyPromise> handle) : _handle(handle) {}
void resume() { _handle.resume(); }
private: std::coroutine_handle<MyPromise> _handle; };
class MyAwaitable { public: MyAwaitable() = default;
bool await_ready() { std::cout << "await ready" << std::endl; return false; } void await_suspend(std::coroutine_handle<> handle) { std::cout << "await suspend" << std::endl; } void await_resume() { std::cout << "await resume" << std::endl; } };
MyCoroutine test_my_coroutine() { std::cout << "start my coroutine" << std::endl; co_await MyAwaitable{}; std::cout << "end my coroutine" << std::endl; }
int main() { MyCoroutine my_routine = test_my_coroutine(); my_routine.resume(); return 0; }
|
上述样例返回
get coroutine handle initial suspend start my coroutine await ready await suspend await resume end my coroutine return void final suspend
|
上面样例中我们创建了一个MyCoroutine
的协程,整个协程运行流程为:
- 首先
test_my_corotine()
这个函数已经变成了一个协程,协程在运行前会先创建一个协程对象MyCorotine
,开始执行承诺对象MyPromise
中相关方法,调用get_return_object
返回协程句柄。
- 然后调用
initial_suspend
,由于返回的是suspend_never
,所以不会挂起协程,直接开始执行协程。
- 在执行到
co_await MyAwaitable
处停止,执行MyAwaitable
中相关函数,先执行await_ready
方法,返回的是false
,协程需要挂起。
- 协程挂起,执行
await_suspend
,控制权返回给main
函数。
- 协程调用
resume
方法,协程重新执行,MyAwaitable
对象执行await_resume
。
- 继续执行协程函数。
- 协程函数默认调用
co_return
,协程对象中承诺对象执行reture_void
。
- 协程执行完,最后承诺对象调用
final_suspend
。
co_return关键字
协程的返回操作,协程执行到最后会默认添加一个co_return
,co_return
调用会触发承诺对象调用return_void
,也可以主动调用co_return expr
返回一个值,会触发调用承诺对象调用return_value(expr)
。
co_yield关键字
co_yield
返回一个值给调用者,同时挂起当前的协程。它实际是co_await
的语法糖形式:
co_await promise.yield_value(expr)
如果想使用co_yield
,我们需要给承诺对象提供一个yield_value
函数,并且该函数返回一个awaitable
对象,这个语法通常用来实现一个惰性生成器。
斐波拉契数列生成器
下面实现了一个斐波拉契数列生成器:
#include <iostream> #include <coroutine>
class FiboCoroutine { public: class FiboPromise { public: FiboCoroutine get_return_object() { return std::coroutine_handle<FiboPromise>::from_promise(*this); } std::suspend_always initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() {
} void return_void() { std::cout << "return void" << std::endl; }
std::suspend_always yield_value(size_t value) { _value = value; std::cout << "yield value: " << value << std::endl; return {}; }
size_t _value = 0; };
using promise_type = FiboPromise; FiboCoroutine(std::coroutine_handle<FiboPromise> handle) : _handle(handle) {}
size_t get_val() { _handle.resume(); return _handle.promise()._value; }
private: std::coroutine_handle<FiboPromise> _handle; };
FiboCoroutine get_fibo() { co_yield 1; co_yield 1; size_t p1 = 1; size_t p2 = 1; size_t p3 = 0; for(int i = 0; ; i++){ p3 = p1 + p2; co_yield p3; p1 = p2; p2 = p3; } }
int main() { FiboCoroutine fibo = get_fibo(); for(int i = 0; i < 10; ++i){ size_t val = fibo.get_val(); std::cout << "fibo " << i << " " << val << std::endl; } return 0; }
|
这里需要注意承诺对象中的init_suspend
返回suspend_always
。
参考