4

The Coroutine in C++ 20 协程之诺

 3 years ago
source link: https://zhuanlan.zhihu.com/p/239792492
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

The Coroutine in C++ 20 协程之诺

一个人NB的不是标签

本文接着The Coroutine in C++ 20 协程初探,继续唠唠C++里面的Coroutine。

在上文中,我们看到如下的reply() coroutine

sync<int> reply()
{
    std::cout << "Started await_answer" << std::endl;
    auto a = co_await read_data();
    std::cout << "Data we got is " << a  << std::endl;
    auto v = co_await write_data();
    std::cout << "write result is " << v << std::endl;
    co_return 42;
}

要让这个函数正确运行,我们需要sync<int>包含一个promise_type,而这个promise_type提供下面的接口

  • initial_suspend()
  • get_return_object()
  • final_suspend()
  • return_value()
  • return_void() (如果coroutine最后返回的是void)
  • yield_value() (如果coroutine里面有co_yield)

这个promise_type是我们与coroutine进行交互的媒介,而这些接口则控制着reply coroutine的行为。下面详细分析——

coroutine:将函数切分

正如Coroutine, 异步,同步,async, await所讲,coroutine是可以被suspend和resume的函数。我们可以形象地理解,借助coroutine,将函数切分为几块,这样我们可以先运行一块,suspend这个coroutine,去干些不相关的活,等条件成熟的时候,比如IO ready了,我们就将这个coroutine resume继续原来的活。如下图,左边灰色的方框是reply() coroutine,右边橙色背景的是promise type。reply() coroutine蓝色的方框表示coroutine的代码,而黄色的方框则是我们可以将这个coroutine suspend 的位置。

v2-e4fc2e92bd7c0c81f7b4b9cc2bd722ab_720w.jpg

在上图,reply() coroutine有两个suspend 位置:

  • inital_suspend point
  • final_suspend point

initial_supsend point顾名思义,就是最开始的地方,编译器会将promise_type的initial_suspend()(比如下面的代码)插入到inital_supend point。

        auto initial_suspend()
        {
            Trace t;
            std::cout << "Started the coroutine, don't stop now!" << std::endl;
            return std::experimental::suspend_never{};
        }

initial_suspend()返回std::experimental::suspend_nerver表示coroutine不要挂起,如果返回always,就是要挂起这个coroutine。

同理,final_suspend point表示当coroutine进行到最后的时候,要做什么。在本文的例子里,编译器会将下面的final_suspend插入到final_suspend point

        auto final_suspend() noexcept
        {
            Trace t;
            std::cout << "Finished the coro" << std::endl;
            return std::experimental::suspend_always{};
        }

这里我们return always,表示程序运行到最后的时候将这个coroutine挂起。

从上面的讲解,我们可以看到,编译器会在函数特定的位置插入一些接口,将函数转变成coroutine。而这些接口由promise type提供。这样子程序员就可以将自己希望的逻辑接入到coroutine对应的位置,从而控制coroutine。推荐你尝试运行A Simple C++ Coroutine,从而看到其中的效果。

promise type

接下来,让我们来一起看看神奇的promise type~

A Simple C++ Coroutine将promise type摘出来,贴在进行更好的参照

class promise_type
    {
        T value;
        promise_type()
        {
            Trace t;
            std::cout << "Promise created" << std::endl;
        }
        ~promise_type()
        {
            Trace t;
            std::cout << "Promise died" << std::endl;
        }

        auto get_return_object()
        {
            Trace t;
            std::cout << "Send back a sync" << std::endl;
            return sync<T>{handle_type::from_promise(*this)};
        }
        auto initial_suspend()
        {
            Trace t;
            std::cout << "Started the coroutine, don't stop now!" << std::endl;
            return std::experimental::suspend_never{};
        }
        auto return_value(T v)
        {
            Trace t;
            std::cout << "Got an answer of " << v << std::endl;
            value = v;
            return std::experimental::suspend_never{};
        }
        auto final_suspend() noexcept
        {
            Trace t;
            std::cout << "Finished the coro" << std::endl;
            return std::experimental::suspend_always{};
        }
        void unhandled_exception()
        {
            std::exit(1);
        }
    };

从上图,我们可以看到,promise type 主要有这几个接口,它们会被编译器插入到coroutine特定的位置。所以当我们运行coroutine的时候,运行到特定的位置就会执行这些接口函数。

如何可以更好理解promise type?其实我们可以从它的名字下手。它叫promise,说明它实际上是一种允诺,是对未来的希望,所以它负责的是控制coroutine的行为(就像我们对未来的希望会控制我们的行为),负责提供coroutine的结果给caller,因为这是它对caller的诺言。所以除了上面讲到的接口外,它实际上存储了coroutine的返回值。而当coroutine完成的时候,我们就可以通过promise获取对应的结果。

 struct promise_type
    {
        T value; // 这是我们要存储的coroutine的完成的结果,也就是返回值
        promise_type()
        {
            Trace t;
            std::cout << "Promise created" << std::endl;
        }
}

上面的代码,value成员负责存储coroutine的结果。当我们在coroutine里面调用co_return res的时候,实际上我们将res存储到了value里面,如

        auto return_value(T res)
        {
            Trace t;
            std::cout << "Got an answer of " << res << std::endl;
            value = res;
            return std::experimental::suspend_never{};
        }

而coroutine的调用者,比如read_data() coroutine 的调用者reply() ,就可以获取存储的value成员。怎么获取呢?在开始调用read_data coroutine的时候,调用者会通过get_return_object()获取这个read_data coroutine的handle,通过handle就可以获取read_data courouine的结果了。再举个简单的例子,比如main函数里面,变量a存储的就是从get_return_object返回的handle, 即sync<int>。我们可以通过a.get()获取promise里面的value。

int main()
{
    std::cout<<"Start main().\n";
    auto a = reply();
    return a.get();
}

总结,sync<int>, lazy<std::string>是promise type的handle,调用者通过他们获取coroutine的结果。而promise type则负责控制coroutine的行为,在关键的位置插入特定的接口,实现控制coroutine的运行。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK