C++20中的协程

      协程(coroutine)是一种可以暂停执行以便稍后恢复的函数协程是无堆栈的(stackless),除非编译器对其进行了优化,否则它们的状态将分配在堆上:它们通过返回给调用者来暂停执行,并且恢复执行所需的数据与堆栈分开存储。这允许异步执行顺序代码(sequential code)(例如,无需显式回调即可处理非阻塞I/O),并且还支持惰性计算无限序列(lazy-computed infinite sequences)上的算法和其他用途。

      如果函数定义包含以下任何一项,则该函数为协程:即要定义协程,函数主体中必须存在co_await、co_yield或co_return关键字

      (1).co_await表达式:暂停执行直到恢复。一元运算符co_await暂停协程并将控制权返回给调用者。其操作数是一个表达式,该表达式要么属于定义成员运算符co_await的类类型,要么可以传递给非成员运算符co_await,或者可以通过当前协程的Promise::await_transform转换为这样的类类型。

      (2).co_yield表达式:暂停执行并返回一个值。类似于Python中的yield。co_yield表达式向调用者返回一个值并暂停当前协程:它是可恢复生成器函数(resumable generator functions)的通用构建块。

      (3).co_return语句:完成执行并返回一个值。

      每个协程都必须具有满足如下所述的一些要求的返回类型:

      (1).限制(Restrictions):协程不能使用可变参数、普通返回语句或占位符返回类型(auto或concept)。consteval函数、constexpr函数、构造函数、析够函数和main函数不能是协程

      (2).执行(Execution):

      每个协程都与以下对象相关联

      promise对象:从协程内部进行操作。协程通过此对象提交其结果或异常。promise对象与std::promise没有任何关系。

      协程句柄:从协程外部进行操作。这是一个非拥有句柄,用于恢复协程的执行或销毁协程框架。

      协程状态:即内部的、动态分配的存储(除非优化了分配),包含的对象:promise对象;参数(全部按值拷贝);当前暂停点的一些表示,以便恢复知道从哪里继续,销毁知道范围内有哪些局部变量;局部变量和临时变量,其生存期跨越当前暂停点。

      当协程开始执行时,它会执行以下操作

      使用operator new分配协程状态对象。

      将所有函数参数复制到协程状态:值参数被移动或复制,引用参数保持引用(by-value parameters are moved or copied, by-reference parameters remain references)。

      调用promise对象的构造函数。如果promise类型有一个接受所有协程参数的构造函数,则使用复制后(post-copy)的协程参数调用该构造函数。否则,将调用默认构造函数。

      调用promise.get_return_object()并将结果保存在局部变量中。当协程首次暂停时,该调用的结果将返回给调用者。在此步骤之前(包括此步骤)抛出的任何异常都会传播回调用者,而不是放置在promise中。

      调用promise.initial_suspend()并co_awaits其结果。典型的promise类型要么返回std::suspend_always(用于延迟启动的协程),要么返回 std::suspend_never(用于急切启动的协程)。

      当co_await promise.initial_suspend()恢复时,开始执行协程的主体。

      当协程到达暂停点时:

      如果有必要,将先前获得的返回对象隐式转换为协程的返回类型后,返回给调用者/恢复者(caller/resumer)。

      当协程到达co_return语句时,它会执行以下操作:

      调用promise.return_void()来获取co_return; co_return expr; 其中expr的类型为void

      或对co_return expr; 调用promise.return_value(expr);其中expr具有非void类型

      按照创建时的相反顺序销毁所有具有自动存储持续时间(automatic storage duration)的变量。

      调用promise.final_suspend()并co_awaits结果。

      如果协程以未捕获的异常结束,它将执行以下操作:

      捕获异常并从catch块中调用promise.unhandled_exception()

      调用promise.final_suspend()并co_awaits结果。从此时恢复协程是未定义的行为。

      当协程状态由于通过co_return或未捕获的异常终止或者由于通过其句柄被销毁而被销毁时,它会执行以下操作:

      调用promise对象的析构函数。

      调用函数参数副本(function parameter copies)的析构函数。

      调用operator delete释放协程状态使用的内存。

      将执行权转回给调用者/恢复者。

      (3).动态分配(Dynamic allocation):

      协程状态通过non-array operator new动态分配。

      如果Promise类型定义了类级别的替换(class-level replacement),则会使用它,否则将使用全局operator new。

      如果Promise类型定义了一个采用附加参数的operator new的放置形式(placement form),并且它们与一个参数列表匹配,其中第一个参数是请求的大小,其余的是协程函数参数,那么这些参数将被传递给operator new。

      如果分配失败,协程将抛出std::bad_alloc,除非Promise类型定义成员函数Promise::get_return_object_on_allocation_failure()。如果定义了该成员函数,则分配使用operator new的nothrow形式,并且在分配失败时,协程会立即将从Promise::get_return_object_on_allocation_failure()获得的对象返回给调用者。

      Promise:Promise类型由编译器使用std::coroutine_traits根据协程的返回类型来确定。

      在C++23中支持了std::generator后,再使用协程会方便很多

      以下为测试代码:主要来自于cppreference

namespace {
// reference: https://en.cppreference.com/w/cpp/language/coroutines
template<typename T>
struct Generator { // In C++23, you can use #include <generator>
    // The class name 'Generator' is our choice and it is not required for coroutine
    // magic. Compiler recognizes coroutine by the presence of 'co_yield' keyword.
    // You can use name 'MyGenerator' (or any other name) instead as long as you include
    // nested struct promise_type with 'MyGenerator get_return_object()' method.

    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;

    struct promise_type { // required
        T value_;
        std::exception_ptr exception_;

        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { exception_ = std::current_exception(); } // saving exception

        template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // caching the result in promise
            return {};
        }
        void return_void() {}
    };

    handle_type h_;

    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // The only way to reliably find out whether or not we finished coroutine,
                // whether or not there is going to be a next value generated (co_yield)
                // in coroutine via C++ getter (operator () below) is to execute/resume
                // coroutine until the next co_yield point (or let it fall off end).
                // Then we store/cache result in promise to allow getter (operator() below
                // to grab it without executing coroutine).
        return !h_.done();
    }

    T operator()()
    {
        fill();
        full_ = false; // we are going to move out previously cached
                       // result to make promise empty again
        return std::move(h_.promise().value_);
    }

private:
    bool full_ = false;

    void fill()
    {
        if (!full_) {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propagate coroutine exception in called context

            full_ = true;
        }
    }
};

Generator<int> range(int start, int end)
{
	while (start < end) {
		co_yield start;
		start++;
	}

	// Implicit co_return at the end of this function:
	// co_return;
}

Generator<std::uint64_t> fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;

    if (n > 94)
        throw std::runtime_error("Too big Fibonacci sequence. Elements would overflow.");

    co_yield 0;

    if (n == 1)
        co_return;

    co_yield 1;

    if (n == 2)
        co_return;

    std::uint64_t a = 0;
    std::uint64_t b = 1;

    for (unsigned i = 2; i < n; ++i) {
        std::uint64_t s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}
} // namespace

int test_coroutine()
{
    auto gen = range(0, 10);
    for (int j = 0; gen; ++j)
        std::cout << "value: " << gen() << '\n';

    try {
        auto gen = fibonacci_sequence(10); // max 94 before uint64_t overflows

        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    } catch (const std::exception& ex) {
        std::cerr << "Exception: " << ex.what() << '\n';
    } catch (...) {
        std::cerr << "Unknown exception.\n";
    }
 
	return 0;
}

      执行结果如下图所示:

      GitHubhttps://github.com/fengbingchun/Messy_Test

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值