The Coroutine in C++ 20 协程初探

微信扫一扫,分享到朋友圈

The Coroutine in C++ 20 协程初探

C++已经从C++11演化到了C++20,你还在用C++98吗?

引言

本文将唠唠C++ 20的coroutine,如果对coroutine,async, await不了解的可以,先移步阅读 薛定谔的喵:Coroutine, 异步,同步,async, await
——

Coroutine就是函数,只不过是可以suspend和resume的函数,也就是你可以暂停这个函数的执行,去做其他事情,然后在恰当的时候恢复到你离开的位置继续开始运行。

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;
}

可是要让上面的代码成功编译,不是一件简单的事情
。这里先抛出能让它编译成功的完整的代码,可以暂时忽略具体的细节,感兴趣可以用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 << "Await " << (ready ? "is ready" : "isn't ready") << std::endl;
return this->coro.done();
}
void await_suspend(std::experimental::coroutine_handle<> awaiting)
{
{
Trace t;
std::cout << "About to resume the lazy" << std::endl;
this->coro.resume();
}
Trace t;
std::cout << "About to resume the awaiter" << std::endl;
awaiting.resume();
}
auto await_resume()
{
const auto r = this->coro.promise().value;
Trace t;
std::cout << "Await value is returned: " << r << std::endl;
return r;
}
}
lazy<std::string> read_data()
{
Trace t;
std::cout << "Reading data..." << std::endl;
co_return "billion$!";
}
lazy<int> write_data()
{
Trace t;
std::cout << "Write data..." << std::endl;
co_return 42;
}

因为reply()函数里面有co_await,要包装成coroutine,所以我们要sync<int>支持特定的接口(本文暂时不涉及,具体下一篇文章涉及)。

而read_ data()和write
_data()被co_await了,我们需要lazy<std::string>支持下面三个接口

  1. bool await_read()
  2. auto await_suspend(HandleType handle)
  3. 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。

当把函数suspend的时候,什么时候reply()会被resume呢?程序员需要在await_suspend()里面进行相应的调度代码。比如完整的代码里面,我们就在马上resume了。实际的产品的代码会将coroutine调到到事件循环调度器,比如libuv里面。

而怎么resume呢?很简单,就是await_suspend()的传入参数就是一个reply()coroutine的handle,我们直接handle.resume()就将reply() resume了。是不是很神奇?

所以这三个接口的功能是:

1 await_ ready()示意被co_await 的对象(read_data(),write
_data())要不要将当前的coroutine(reply()) suspend(挂起)。

2 await_suspend()就是决定挂起的时候,定义什么时候可以被resume。

3 await_ resume()就是定义当被 co_await()的对象被恢复的要将什么作为co_await的返回值。

为什么会这么”神奇“

不要问,问就是编译器提供的支持:)

完整的代码read_ data()和write
_data()返回的lazy<std::string>要支持其他的接口,比如initial_ suspend, final_suspend,get_return_object等等
下一篇文章将讲解它们的具体作用:)

附注:

Coroutine包含了await, yield, async。在JavaScript里面一般不叫coroutine,叫async call或者Promise。

C++ 对应有co_ await,co_yield, 还多了个co_
return。

微信扫一扫,分享到朋友圈

The Coroutine in C++ 20 协程初探

A Simple C++ Coroutine

上一篇

你也可能喜欢

The Coroutine in C++ 20 协程初探

长按储存图像,分享给朋友