C++20重量级特性:协程

文摘   2024-12-09 15:26   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

相较于其它编程语言,比如Python/Ruby/Swift等等,协程的概念早早引入了。千呼万唤,C++20也终于引入了协程,但是作为基础性的语言,C++的协程实现代码过于庞杂,处于有而难用的情况。本篇看下C++20的协程。

C++20协程

协程的概念

我们经常接触到进程,线程。线程是进程中的一个执行单元,而协程呢?可以认为是比线程更小的执行单元,且协程可以分割控制函数和代码片段,意即它可以控制代码的细碎的点。比如代码初始化的时候,可以让协程控制其初始化的逻辑,代码挂起的时候,可以控制其代码挂起的逻辑等。

C++20的协程通过promise_type来定义和管理协程的生命周期。比如挂起,恢复,异常处理,返回值等操作。通过co_awaitco_returnco_yield来控制协程,通过以下函数进行实际的运作:

get_return_object(),返回协程的最终结果对象。它通过std::coroutine_handle将promise_type转化为协程对象,通常在返回值时使用。

initial_suspend(),定义协程开始时是否挂起。返回类型为std::suspend_always 时,协程会在开始时挂起;返回std::suspend_never 时,协程开始时不挂起。

final_suspend(),定义协程结束时是否挂起。通常用于决定协程执行完后是否等待其清理工作。

return_void(),协程正常结束时,调用此方法,表示没有返回值(即返回void)

unhandled_exception(),当协程中抛出异常时,会调用此函数。它的主要作用是处理协程中的未处理异常,通常可以选择终止程序或重新抛出异常。

例子1:控制函数

下面用个例子演示下这些概念。

//filename:xiecheng.cpp#include <iostream>#include <coroutine>// 自定义协程类型struct Example {    struct promise_type {        Example get_return_object() { return Example{ std::coroutine_handle<promise_type>::from_promise(*this) }; }        std::suspend_always initial_suspend() {            std::cout << "initial_suspend: Coroutine is suspended at the start.\n";            return {};        }        std::suspend_always final_suspend() noexcept {            std::cout << "final_suspend: Coroutine is suspended at the end.\n";            return {};        }        void return_void() {}        void unhandled_exception() { std::terminate(); }    };    std::coroutine_handle<promise_type> handle;    explicit Example(std::coroutine_handle<promise_type> h) : handle(h) {}    ~Example() { if (handle) handle.destroy(); }    void resume() { handle.resume(); }    bool done() const { return handle.done(); }};// 协程函数Example coroutine_function() {    std::cout << "Coroutine body execution begins.\n";    co_await std::suspend_always{};    std::cout << "Coroutine body execution resumes.\n";}int main() {    std::cout << "Main function starts.\n";    auto coro = coroutine_function();    // 协程的初始挂起状态    std::cout << "Resuming coroutine...\n";    coro.resume();    // 协程的最终挂起状态    std::cout << "Finalizing coroutine...\n";    coro.resume();    std::cout << "Main function ends.\n";    getchar();    return 0;}

C++20中以上代码运行出了以下结果:

#g++ -std=c++23 xiecheng.cpp -o xiechengMain function starts.initial_suspend: Coroutine is suspended at the start.Resuming coroutine...Coroutine body execution begins.Finalizing coroutine...Coroutine body execution resumes.final_suspend: Coroutine is suspended at the end.Main function ends.

通过结果,我们可以看到协程严密控制函数的运行。当我们调用coroutine_function函数的时候,首先会被运行的是initial_suspend函数,它的返回值std::suspend_always告诉coroutine_function函数。coroutine_function初始化的时候会被暂停。

后面调用coro.resume的时候恢复被挂起的函数coroutine_function。让这个函数进行运行打印出了:Coroutine body execution begins.。而在此之前coroutine_function完全没有被运行到,只是被初始化了。

下面在coroutine_function函数里面通过代码:co_await std::suspend_always{};又挂起了coroutine_function函数。通过Main函数里面的coro.resume();进行了恢复运行。

从上例子可以看到,协程可以控制函数的初始化,运行过程,以及运行的结果等等。

例子2:值返回

1.co_yield,一次返回一个值。2.co_return返回最终的值。其中的co_return可以作为协程promise_type结构函数return_value和return_void的返回值。

//filename:xiecheng.cpp#include <iostream>#include <coroutine>#include <vector>// 定义一个协程返回类型struct generator {    struct promise_type {        int current_value;         generator get_return_object() {            return generator{ std::coroutine_handle<promise_type>::from_promise(*this) };        }        // 初始挂起        std::suspend_always initial_suspend() { return {}; }        std::suspend_always final_suspend() noexcept { return {}; }        void return_void() {             std::cout << "Coroutine has finished executing.\n";         }        std::suspend_always yield_value(int value) {            current_value = value;            return {};        }        void unhandled_exception() {             std::terminate();         }    };    std::coroutine_handle<promise_type> handle;
explicit generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~generator() { if (handle) handle.destroy(); } bool move_next() { handle.resume(); return !handle.done(); } // 获取当前值 int current_value() const { return handle.promise().current_value; }};
generator count_up_to(int max) { for (int i = 1; i <= max; ++i) { co_yield i; // 每次返回一个值 } //co_return 100; co_return; //返回}int main() { auto gen = count_up_to(5); while (gen.move_next()) { std::cout << gen.current_value() << " "; } std::cout << "\n"; return 0;}
return_void函数表示协程结束了,可以通过co_return来调用(也可以不调用)。co_yield表示每次返回一个值。最后通过调用final_suspend函数表示协程结束了。
# g++ -std=c++23 xiecheng.cpp -o xiecheng1 2 3 4 5 Coroutine has finished executing.

结尾

C++的协程总体来说不是太好控制,且代码过于繁多,对于一些新手理解比较复杂。参考Python协程就清爽了许多,如下:

import asyncio# 定义协程函数async def say_hello():    print("Hello")    await asyncio.sleep(1)  # 模拟异步操作    print("World")# 运行协程async def main():    await say_hello()# 运行事件循环asyncio.run(main())

对于实际上的应用,依然有段距离。所以基本上都是第三方库提供支撑,比如cppcoro,Boost.Coroutine等。

往期精彩回顾

C++20 重量级的特性

C++安全指针,Rust用处何在?



关注公众号↑↑↑:江湖评谈

江湖评谈
记录,分享,自由。
 最新文章