c++20协程
c++20支持协程,但文档有点难理解。研究了两天,总结一下。
定义一个最简单的协程函数:
R co_func() {
co_await awaiter;
}
要求R::promise_type
存在,并且是一个实现了几个规定接口的类型。要提供的接口大致如下:
// 构建R的实例
R get_return_object();
// 准备好协程环境后是否马上挂起
Awaiter initial_suspend();
// 协程结束前是否挂起
Awaiter final_suspend();
// 配合`co_yield expr`
Awaiter yield_value(ValueType);
// 配合`co_return expr`
void return_value(ValueType);
// 配合`co_return;`
void return_void();
// 处理未捕获的异常
void unhandled_exception();
想知道这些接口是用来做什么的,需要先理解整个协程的工作流程。编译器对协程函数处理后的伪代码:
{
promise-type promise promise-constructor-arguments ;
try {
co_await promise.initial_suspend() ;
function-body
} catch ( ... ) {
if (!initial-await-resume-called)
throw ;
promise.unhandled_exception() ;
}
final-suspend :
co_await promise.final_suspend() ;
}
而理解这段代码要先理解co_await
机制,也就是协程机制,特别地,c++提供的是无栈协程。
简单来说,co_await
需要后接一个awaiter
,这个awaiter
就是传统意义上的异步任务,通常是需要时间来完成的,因此协程在这个任务工作的时候会主动把程序控制权交出去。但有时候任务根本立马就能完成,例如读一个非阻塞的socket
,兴许缓冲区里本身就有数据,那就不需要把协程切出去了。
因此,awaiter
也被要求实现几个接口:
// 任务是否已经完成
bool await_ready();
// 协程挂起后的回调
void await_suspend(std::coroutine_handle<> h);
// 协程被重新唤醒后的回调
void await_resume();
其中await_resume
的返回值类型并非一定要是void
,它的返回值就是co_await awaiter;
的返回值,例如你大可以返回其他东西,以表示任务的结果。
同样,await_suspend
的返回值也并非一定要是void
,但情况有点多,当你有些奇思妙想的时候,不妨看看文档。
std::coroutine_handle<> h
很简单,就是协程句柄,通过它我们可以唤醒、销毁协程之类,文档在此。
现在回过头来看编译器处理过后的那段伪代码,在协程开始前调用了co_await promise.initial_suspend();
,通常你看其他人的代码,promise.initial_suspend()
不是返回std::suspend_always
,就是返回std::suspend_never
。这两个是利用await_ready
人为制造的空任务,std::suspend_always
是指让协程切出去,std::suspend_never
则是让协程继续运行。
所以,通过promise_type::initial_suspend
可以控制协程在执行代码前是否先把控制权交还给调用者,或者调用者可以做些准备工作,也或许调用者就想晚点再调用。算是框架提供了一点灵活性吧。
promise_type::final_suspend
类似,不过注意,假如此时把控制权交了出去,则其他人要负责销毁协程。
promise_type::yield_value
也需要返回一个awaiter
,这因为co_yield
实际是用co_await
实现的,co_yield expr
实际上等于co_await promise.yield_value(expr)
。通常就返回std::suspend_always
了,我也没想到什么情况下yield_value
之后不需要挂起协程。感觉这就不算什么灵活性了,只是框架实现方便罢了。
差不多就这些,要注意的地方还是有点多,还是找个别人封装好的库吧。