C++20中的Coroutine(二)

Tue 20 August 2019 / In categories Programming

C++20, Coroutine

本文接着探讨准备进入C++20的Coroutine。

如何理解Coroutine

上一篇文章提到,Coroutine和普通函数有所不同,可以多次挂起和恢复,所以需要用和对待普通函数不同的眼光来看待Coroutine。普通函数单入单出,如果把普通函数比作一个巧克力球,那么这个巧克力球就是那种可以一口吃掉的巧克力球。而Coroutine则,则可以看成那种可以分为小份的巧克力块,可以每次支持一个小份。成功理解这点是理解Coroutine的关键。分成多份的巧克力块可以给同一个人分多次吃,这其实相当于Coroutine中类似Generator的应用。如果把巧克力块其中的某些份分给其他人吃,这就相当于把函数的不同部分分给不同的线程来运行。当然,写Coroutine的人可以决定巧克力块的食用顺序。有的人会觉得好吃的巧克力为什么要分给别人吃?如果吃巧克力是一种工作的话,那么找人分担是不是一种更好的做法呢?

简而言之,可以把Coroutine当成是多个函数拼接在一起所生成的一种新的形态,就像乐高积木那样。

如何让普通函数成为Coroutine

那么在C++中,如何让普通的函数成为Coroutine呢?很简单,只要使用到了co_await、co_yeild、co_return这三个关键字中的一个,这个函数就会被编译器当作Coroutine。

来看下面这一个例子:

future<void> sweet() {
cout << "header" << endl;
co_await suspend_always{};
cout << "middle" << end;
co_await suspend_always{};
cout << "footer" << endl;
}

上面示意的例子中,用到了两个co_await suspend_always{}语句,每个语句其实创建了一个Coroutine的挂起点。执行co_await suspend_always{}之后,该Coroutine被挂起而停止执行,直到被恢复为止。而两个co_await suspend_always{}把该Coroutine分成了三份,执行后分别为输出header、middle以及footer。

suspend_always目前定义在std::experimental空间,用于co_await时会挂起coroutine。它还有个兄弟叫做suspend_never,执行co_await的时候不会挂起,相当于没有效果,在特殊的情境下有用。

如果一个函数时Coroutine,编译器会对其添油加醋,上面的sweet()例子会被扩展成:

future<void> sweet()
{
  __sweet_context* __context = new __sweet_context{};
  __return = __context->_promise.get_return_object();
  co_await __context->_promise.initial_suspend();

  cout << "header" << endl;
  co_await suspend_always{};
  cout << "middle" << end;
  co_await suspend_always{};
  cout << "footer" << endl;

__final_suspend_label:
  co_await __context->_promise.final_suspend();
}

上面的例子参考自Introduction to C++ Coroutines Slides。其中sweet_context时编译器生成的一个上下文,用于保存coroutine挂起还原时所需要的动态空间(如果不需要这个空间,编译器完全可以把这个分配操作优化掉)。sweet()的返回类型是future<void>,但实际上,该返回类型是通过__return = __context->_promise.get_return_object();创建的return对象。

在正式执行coroutine之前,会先执行co_await __context->_promise.initial_suspend();来判断是否要一开始就挂起coroutine,如果不需要,那么initial_suspend()可以返回suspend_never对象。在coroutine结束之前,则会执行co_await __context->_promise.final_suspend();,看是否需要结束前挂起。总之,编译器对coroutine的拆分程度,比你想象得高。

initial_suspend和final_suspend都来自__context->_promise,那么_promise是什么类型呢?其实_promise的类型来自于future<void>。编译器会去futuer<void>下搜寻是否有struct promise_type,如果有,则把它当作_promise的类型。

欲知struct promise_type的具体内容,请听下回分解。

CppCon

Spec

Other

Stackful vs Stackless

(本篇完)

Load Disqus Comments