|
C++已经从C++11演化到了C++20,你还在用C++98吗? 引言
本文将唠唠C++ 20的coroutine(协程),如果对coroutine,async, await不了解的,可以先移步阅读Coroutine, 异步,同步,async, await——
Coroutine就是广义上的函数,只不过是可以suspend和resume的函数,也就是你可以暂停这个函数的执行(实际上就是在suspend的地方直接返回到caller了),去做其他事情,然后在恰当的时候恢复到你离开的位置继续开始运行。
如下图左边灰色的coroutine(特别点的函数),当它被调用的时候,被切分成了两个部分,在线程1执行完成第一部分后,就继续做其他事情了,第二部分被suspend了。在适当的时机出现后,线程2(可以是线程1)开始执行第二部分。

把一个完整的函数切分成多个部分,有什么好处呢?请看Coroutine, 异步,同步,async, await。本文关注C++是如何实现这样的效果~
JavaScript里面的coroutine使用起来很方便,长这个样子
async reply() {
get_name(); //normal function call
data = await read_data();
res = await write(data);
return res;
}
它们长得跟同步的代码很像,只是在block的函数调用前面添加了await关键字,而函数本身reply()标注了async关键字。它们执行的逻辑顺序不变(也就是按照代码书写的顺序执行)但是实际是异步执行的。具体可以参见Coroutine, 异步,同步,async, await。
C++的coroutine长什么样子呢?
Coroutine in C++
C++虽然是接近底层的编程语言,但C++ coroutine代码长得也差不多,如下
sync<int> reply() {
get_name(); // normal function call
std::string data = co_await read_data();
int res = co_await write(data);
return res;
}
可是要让上面的代码成功编译,不是一件简单的事情。这里先抛出能让它编译成功的完整的代码,可以暂时忽略具体的细节,感兴趣可以用VS2015/2019, Clang/GCC 10.0去尝试编译一下。发现太长了,我把它单独放到这里:A Simple C++ Coroutine
代码我加了Trace,方便跟踪程序的执行顺序。如果点进去随便瞄一下,会发现代码很长,第一次接触肯定一脸o((⊙﹏⊙))o 。下面具体分解。
首先reply()函数就叫coroutine,也就是它可以suspend,然后resume。那什么支持了它这么灵活呢?
大体上两个方面,一个编译器的支持,也就是完整的代码(指这里的代码:A Simple C++ Coroutine,下文意同)需要比较新的C++编译器才能编译;另一个方面是程序员按照跟编译器的约定编写特定的代码。
把函数变成Coroutine
将本文要讲解的代码从完整的代码摘出来如下:
template<typename T>
class lazy {
bool await_ready()
{
const auto ready = this->coro.done();
Trace t;
std::cout << &#34;Await &#34; << (ready ? &#34;is ready&#34; : &#34;isn&#39;t ready&#34;) << std::endl;
return this->coro.done();
}
void await_suspend(std::experimental::coroutine_handle<> awaiting)
{
{
Trace t;
std::cout << &#34;About to resume the lazy&#34; << std::endl;
this->coro.resume();
}
Trace t;
std::cout << &#34;About to resume the awaiter&#34; << std::endl;
awaiting.resume();
}
auto await_resume()
{
const auto r = this->coro.promise().value;
Trace t;
std::cout << &#34;Await value is returned: &#34; << r << std::endl;
return r;
}
}
lazy<std::string> read_data()
{
Trace t;
std::cout << &#34;Reading data...&#34; << std::endl;
co_return &#34;billion$!&#34;;
}
lazy<int> write_data()
{
Trace t;
std::cout << &#34;Write data...&#34; << std::endl;
co_return 42;
}
sync<int> reply(int i)
{
std::cout << &#34;Started await_answer&#34; << std::endl;
auto a = co_await read_data();
std::cout << &#34;Data we got is &#34; << a << std::endl;
auto v = co_await write_data();
std::cout << &#34;write result is &#34; << v << std::endl;
co_return 42;
}
因为reply()函数里面有co_await,要包装成coroutine,所以我们要sync<int>支持特定的接口,具体介绍放在单独的文章:The Coroutine in C++ 20 协程之诺。
而read_data()和write_data()被co_await了,我们需要它们的返回的lazy<std::string>支持下面三个接口
- bool await_ready()
- auto await_suspend(HandleType awaiting)
- auto await_resume()
其中 using HandleType = std::experimental::coroutine_handle<>有了这三个接口,当reply() coroutine 进行co_await需要suspend的时候,await_suspend就被调用;当函数resume的时候,await_resume()就会被调用。
而函数什么时候会被suspend呢?当await_ready()返回false的时候,所以程序员需要在await_ready里面定义该不该suspend, 如:
bool await_ready()
{
const auto ready = this->coro.done();
Trace t;
std::cout << &#34;Await &#34; << (ready ? &#34;is ready&#34; : &#34;isn&#39;t ready&#34;) << std::endl;
return this->coro.done();
}
当把函数suspend的时候,什么时候reply()会被resume呢?程序员需要在await_suspend()里面编写相应的调度代码。比如完整的代码里面,我们就马上resume了:
void await_suspend(std::experimental::coroutine_handle<> awaiting)
{
//...省略部分代码
Trace t;
std::cout << &#34;About to resume the reply()&#34; << std::endl;
awaiting.resume();
}
实际的产品的代码会将coroutine (reply())放到到事件调度器,比如libuv(NodeJS的时间循环,我正在利用C++ 20 Coroutine使得libuv支持co_await,后续会讲解如何实现)。
而怎么resume呢?很简单,await_suspend()的传入参数就是一个reply() coroutine的handle,我们直接awaiting.resume()就将reply() resume了,见上面的代码。
是不是很神奇?
所以这三个接口的功能是:
1 await_ready()示意被co_await 的对象(read_data(),write_data())要不要将当前的coroutine (reply) suspend(挂起)。
2 await_suspend()负责当挂起的时候,定义什么时候可以被resume。
3 await_resume()定义当被 co_await的对象resume时要将什么作为co_await的返回值。
为什么会这么”神奇“
完整的代码read_data()和write_data()返回的lazy<std::string>还需要一些工作,比如initial_suspend, final_suspend,get_return_object等等,The Coroutine in C++ 20 协程之诺将继续讲解~
附注
Coroutine关键词有await, yield, async。在JavaScript里面一般不叫coroutine,叫async call或者Promise。
C++ 对应有co_await,co_yield, 还多了个co_return。async对应什么呢?请看我的续篇The Coroutine in C++ 20 协程之诺
参考文章:
- C++ Coroutines: Understanding operator co_await
- Awaiting
|
|