coroutine, it's fun
协程这东西虽然前面已经讲过很多次了,不过都是讲的有栈协程,这里说说C++20的无栈协程。
C++20引入了协程,带来了三个关键字co_await
、co_yield
以及co_return
,两个结构coroutine_traits
和coroutine_handle
。按照标准的定义,如果一个函数体内使用了以上提到的任意一个关键字,那么这个函数就是一个coroutine,main函数不能是coroutine。和其他语言提供的协程相比C++提供的更加底层,优点是库作者有更多发挥的空间,可以用来实现各种类型的coroutine,比如 生成器,go协程等等;缺点是对普通用户来说无法应用,对库作者来说需要主要更多的细节。
一些概念
coroutine_traits
可以看作是一个约束,实际上长这样
template<typename R, typename... Args> struct coroutine_traits;
协程的类型应该是
R::promise_type coroutine(Args&&...)
coroutine_handle
可以看作是编译器暴露的接口,用户可以利用这个handle恢复(resume)协程,也可以通过from_promise()
和 promise()
两个函数和promise_type
之间转化。
awaitable
我们看看标准自带的suspend_always
的定义:
struct suspend_always
{
bool await_ready() { return false; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
如果一个类型包含上面三个成员函数,或者定义了operator co_await
返回这样的类型,这里称这些类型为awaitable
。
- 如果
await_ready
返回true,那么立即suspend当前协程,否则继续执行 - 如果
await_suspend
返回类型为void,那么立即跳转到调用者
- 如果
await_suspend
返回类型为bool,且为true,立即跳转到调用者 - 如果
await_suspend
返回类型为coroutine_handle
,则调用handle.resume()
await_resume
的返回值即为co_await
最终的值,用于promise_type::return_void
或promise_type::return_value
promise_type
一个最简单的promise_type
可能有如下的结构
struct promise_type
{
auto get_return_object();
auto initial_suspend();
auto finial_suspend();
void return_void();
void unhandled_exception();
};
- 如果要返回值则需要有
void return_value(auto)
不能与return_void
同时存在 - 如果需要使用
co_yield
需要有void yield_value(...)
最简单的coroutine
有了前面的铺垫,现在就可以很轻松的写出第一个协程了
#include <coroutine>
#include <iostream>
struct co
{
struct promise_type;
std::coroutine_handle<promise_type> h;
co(std::coroutine_handle<promise_type> h) : h{h} {}
~co() { h.destroy(); }
struct promise_type
{
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() { return {}; }
co get_return_object()
{
return std::coroutine_handle<promise_type>::from_promise(*this);
}
void unhandled_exception() {}
};
void resume()
{
h.resume();
}
};
co my_coroutine()
{
std::cout << "enter\n";
co_await std::suspend_always{};
std::cout << "leave\n";
}
int main()
{
auto c = my_coroutine();
c.resume();
std::cout << "end\n";
}
编译运行会发现”leave”没有打印,把 suspend_always
改为suspend_never
后就打印出来了,符合前面awaitable
的描述。
生成器
非对称协程的代表,略。
网络库
前面提到过很多次了,协程就是为了减少线程调度才用的,相比有栈协程,C++的无栈协程其实就是个状态机,只需要极少的内存占用,函数调用的额外开销,用在网络编程可以进一步增加扩展性,Gor Nishanov同志把这叫做:a negative overhead abstraction。
代码见这里 https://github.com/abbycin/conet 贴张图好了
注意
- 生命周期
coroutine is destroyed when:
- the final-suspend resumed, or
- coroutine_handle<>::destroy() is called 例如:
- data race
- 栈溢出 symmetric transfer
建议观看
- CppCon 2016: James McNellis “Introduction to C++ Coroutines”
- CppCon 2016: Gor Nishanov “C++ Coroutines: Under the covers”
- CppCon 2017: Gor Nishanov “Naked coroutines live (with networking)”
- CppCon 2018: G. Nishanov “Nano-coroutines to the Rescue! (Using Coroutines TS, of Course)”
补一个完整点的