C ++ 20中协程的机制是什么?

Cur*_*ous 21 c++ asynchronous coroutine c++20 c++-coroutine

我试图阅读有关在调用,暂停,恢复和终止协程函数时调用的操作顺序的文档(功能部件本身的cppreference和标准文档)。该文档深入概述了各种扩展点,这些扩展点允许库开发人员使用库组件自定义协程的行为。从高级的角度来看,这种语言功能似乎是经过深思熟虑的。

不幸的是,我在遵循协程执行机制方面确实很难,而且作为图书馆开发人员,我如何使用各种扩展点来定制所述协程的执行。甚至从哪里开始。

我不完全了解的新定制点集中包含以下功能:

  • initial_suspend()
  • return_void()
  • return_value()
  • await_ready()
  • await_suspend()
  • await_resume()
  • final_suspend()
  • unhandled_exception()

有人可以在高级伪代码中描述运行用户协程时编译器生成的代码吗?在抽象的层面,我想弄清楚的时候功能,如await_suspendawait_resumeawait_readyawait_transformreturn_value,等叫,他们所服务的目的是什么,我怎么可以用它们来写协同程序库。


不确定这是否是题外话,但是这里的一些入门资源对于整个社区来说都是非常有用的。像在cppcoro中一样四处搜寻并深入研究库实现并不能帮助我克服最初的障碍:(

dor*_*ron 15

N4775概述了针对C ++ 20的协程的建议。它介绍了许多不同的想法。以下是我的博客,网址https://dwcomputersolutions.net。更多信息可以在我的其他帖子中找到。

在检查整个Hello World协程程序之前,请逐步进行各个步骤。这些包括:

  1. 协程承诺
  2. 协程上下文
  3. 协程的未来
  4. 协程手柄
  5. 协程本身
  6. 实际使用协程的子例程

整个文件包含在这篇文章的结尾。

协程

Future f()
{
    co_return 42;
}
Run Code Online (Sandbox Code Playgroud)

我们用实例化协程

    Future myFuture = f();
Run Code Online (Sandbox Code Playgroud)

这是一个简单的协程,只返回值42。这是一个协程,因为它包含关键字co_return。具有关键字的任何功能 co_awaitco_return或者co_yield是一个协程。

您会注意到的第一件事是,尽管我们返回的是整数,但协程返回类型是(用户定义的)Future类型。原因是,当我们调用协程时,我们现在不运行该函数,而是初始化一个对象,该对象最终将为我们提供我们正在寻找Future的值。

查找约定的类型

实例化协程时,编译器要做的第一件事是找到表示此协程特定类型的promise类型。

通过创建以下模板的部分专业化,我们告诉编译器什么promise类型属于什么协程函数签名

template <typename R, typename P...>
struct coroutine_trait
{};

with a member called `promise_type` that defines our Promise Type
Run Code Online (Sandbox Code Playgroud)

对于我们的示例,我们可能想要使用类似以下的内容:

template<>
struct std::experimental::coroutines_v1::coroutine_traits<Future> {
    using promise_type = Promise;
};
Run Code Online (Sandbox Code Playgroud)

在这里,我们创建了一个coroutine_trait没有指定参数和返回类型的特殊化Future,这与我们的协程函数签名完全匹配 Future f(void)promise_type然后是诺言类型,在我们的例子中是struct Promise

现在是用户了,coroutine_trait 因为协程库提供了一种很好的简单方法来指定promise_typeFuture类本身,所以我们通常不会创建自己的专业化名称。以后再说。

协程上下文

正如我在上一篇文章中提到的那样,由于协程是可挂起的和可恢复的,因此局部变量不能总是存储在堆栈中。为了存储非堆栈安全的局部变量,编译器将在堆上分配一个Context对象。我们的Promise实例也会被存储。

承诺,未来和处理

协程通常是无用的,除非它们能够与外界通信。我们的承诺告诉我们协程应该如何表现,而我们的未来对象允许其他代码与协程进行交互。然后,“承诺与未来”将通过我们的协程进行沟通。

承诺

一个简单的协程承诺看起来像:

struct Promise 
{
    Promise() : val (-1), done (false) {}
    std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
    std::experimental::coroutines_v1::suspend_always final_suspend() {
        this->done = true;
        return {}; 
    }
    Future get_return_object();
    void unhandled_exception() { abort(); }
    void return_value(int val) {
        this->val = val;
    }

    int val;
    bool done;    
};

Future Promise::get_return_object()
{
    return Future { Handle::from_promise(*this) };
}
Run Code Online (Sandbox Code Playgroud)

如前所述,当协程被实例化并在协程的整个生命周期中退出时,将分配promise。

完成后,编译器将调用get_return_object此用户定义的函数,该函数负责创建Future对象并将其返回给协程实例化器。

在我们的实例中,我们希望我们的Future能够与协程进行通信,因此我们使用协程的手柄来创建Future。这将使我们的未来能够访问我们的承诺。

创建协程后,我们需要知道是要立即开始运行它还是要立即保持挂起状态。这是通过调用Promise::initial_suspend()函数来完成的。此函数返回一个Awaiter,我们将在另一篇文章中查找。

在我们的例子中,由于我们确实希望函数立即启动,因此我们调用 suspend_never。如果我们暂停了该函数,则需要通过在句柄上调用resume方法来启动协程。

我们需要知道co_return在协程中调用运算符时该怎么做。这是通过return_value功能完成的。在这种情况下,我们将值存储在Promise中,以便以后通过Future检索。

如果发生异常,我们需要知道该怎么做。这是通过unhandled_exception函数完成的 。因为在我们的示例中,不应发生异常,所以我们只是中止了。

最后,我们需要知道在破坏协程之前该怎么做。这是通过进行的。final_suspend function在这种情况下,由于我们要检索结果,所以我们返回suspend_always。然后必须通过协程处理destroy方法销毁协程。否则,如果我们返回 suspend_never,协程将在运行结束后立即销毁。

手柄

手柄可以访问协程及其承诺。有两种味道,当我们不需要访问promise时使用void句柄,当我们需要访问promise时使用带有promise类型的协程句柄。

template <typename _Promise = void>
class coroutine_handle;

template <>
class coroutine_handle<void> {
public:
    void operator()() { resume(); }
    //resumes a suspended coroutine
    void resume();
    //destroys a suspended coroutine
    void destroy();
    //determines whether the coroutine is finished
    bool done() const;
};

template <Promise>
class coroutine_handle : public coroutine_handle<void>
{
    //gets the promise from the handle
    Promise& promise() const;
    //gets the handle from the promise
    static coroutine_handle from_promise(Promise& promise) no_except;
};
Run Code Online (Sandbox Code Playgroud)

未来

未来看起来像这样:

class [[nodiscard]] Future
{
public:
    explicit Future(Handle handle)
        : m_handle (handle) 
    {}
    ~Future() {
        if (m_handle) {
            m_handle.destroy();
        }
    }
    using promise_type = Promise;
    int operator()();
private:
    Handle m_handle;    
};

int Future::operator()()
{
    if (m_handle && m_handle.promise().done) {
        return m_handle.promise().val;
    } else {
        return -1;
    }
}
Run Code Online (Sandbox Code Playgroud)

Future对象负责将协程抽象到外部世界。我们有一个构造函数,根据诺言的get_return_object实现从诺言中获取句柄。

破坏者会破坏协程,因为在我们的案例中,控制就是承诺的生命。

最后我们有一行:

using promise_type = Promise;
Run Code Online (Sandbox Code Playgroud)

coroutine_trait如果我们promise_type在协程的返回类中定义我们的代码,那么C ++库将使我们免于实现自己的实现。

我们终于得到它了。我们的第一个简单协程。

完整资料



#include <experimental/coroutine>
#include <iostream>

struct Promise;
class Future;

using Handle = std::experimental::coroutines_v1::coroutine_handle<Promise>;

struct Promise 
{
    Promise() : val (-1), done (false) {}
    std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
    std::experimental::coroutines_v1::suspend_always final_suspend() {
        this->done = true;
        return {}; 
    }
    Future get_return_object();
    void unhandled_exception() { abort(); }
    void return_value(int val) {
        this->val = val;
    }

    int val;
    bool done;    
};

class [[nodiscard]] Future
{
public:
    explicit Future(Handle handle)
        : m_handle (handle) 
    {}
    ~Future() {
        if (m_handle) {
            m_handle.destroy();
        }
    }
    using promise_type = Promise;
    int operator()();
private:
    Handle m_handle;    
};

Future Promise::get_return_object()
{
    return Future { Handle::from_promise(*this) };
}


int Future::operator()()
{
    if (m_handle && m_handle.promise().done) {
        return m_handle.promise().val;
    } else {
        return -1;
    }
}

//The Co-routine
Future f()
{
    co_return 42;
}

int main()
{
    Future myFuture = f();
    std::cout << "The value of myFuture is " << myFuture() << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

等候人员

co_await运营商允许我们暂停我们的协同程序和返回控制返回给调用者协程。这使我们可以在等待操作完成的同时做其他工作。完成后,我们可以从上次中断的地方继续恢复。

co_await运算符可以通过多种方式来处理其右侧的表达式。现在,我们将考虑最简单的情况,那就是 co_await表达式返回一个Awaiter的地方。

一个Awaiter是一个简单的structclass实现了以下方法:await_readyawait_suspendawait_resume

bool await_ready() const {...}只是返回是否准备好恢复协程或是否需要暂停协程。假设 await_ready返回false。我们开始跑步await_suspend

await_suspend方法有几个签名。最简单的是void await_suspend(coroutine_handle<> handle) {...}。这是我们co_await将暂停的协程对象的句柄。此函数完成后,控制权将返回给协程对象的调用方。正是这个功能负责存放协程手柄,以便以后使协程不会永远保持悬浮状态。

一旦handle.resume()被调用;await_ready返回false;或其他某种机制恢复我们的协程,auto await_resume()则调用该方法。的返回值await_resumeco_await操作员返回的值。有时,如上所述,expr in co_await expr返回一个等待者是不切实际的。如果expr返回一个类,则该类可以提供自己的Awaiter operator co_await (...) which will return the Awaiter. Alternatively one can implement anawait_transform method in ourpromise_type 实例,该实例会将 expr转换为Awaiter。

既然我们已经对Awaiter进行了描述,我想指出的是,我们两个返回Awaiter 的 initial_suspendfinal_suspend方法promise_type。对象suspend_alwayssuspend_never是琐碎的等待者。 suspend_always返回true await_readysuspend_never返回false。但是,没有什么可以阻止您推出自己的产品。

如果您想知道现实生活中的侍应生是什么样子,请看一下我未来的对象。它将协程手柄存储在一个lamda中,以供以后处理。

  • 感谢您的详细解答!因为这个主题对我来说非常重要,正确理解,只是为了确保我尽力获得我希望的参与,我将在选项打开时打开赏金,然后在结尾。 (3认同)