C++20协程示例

C++20协程示例

认识协程

在C++中,协程就是一个可以暂停和恢复的函数。

包含co_waitco_yieldco_return关键字的都可以叫协程。

看一个例子:

c++ 复制代码
MyCoroGenerator<int> testFunc(int n)
{
    std::cout << "Begin testFunc" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cout << "TestFunc before yield " << i << std::endl;
        co_yield i;
        std::cout << "TestFunc after yield " << i << std::endl;
    }
    std::cout << "End testFunc" << std::endl;
}

int main()
{
    int inp = 10;
    std::cout << "Before testFunc" << std::endl;
    MyCoroGenerator<int> gen = testFunc(inp);
    std::cout << "After testFunc" << std::endl;
    for (int i = 0; i < inp; ++i) {
        std::cout << "Cur input: " << i << std::endl;
        std::cout << "Output value: " << gen.next() << std::endl;
        std::cout << "After input: " << i << std::endl;
    }
}

上面这段代码的执行结果是:

txt 复制代码
Before testFunc
After testFunc
Cur input: 0
Output value: Begin testFunc
TestFunc before yield 0
0
After input: 0
Cur input: 1
Output value: TestFunc after yield 0
TestFunc before yield 1
1
After input: 1
Cur input: 2
Output value: TestFunc after yield 1
TestFunc before yield 2
2
After input: 2
Cur input: 3
Output value: TestFunc after yield 2
TestFunc before yield 3
3
After input: 3
Cur input: 4
Output value: TestFunc after yield 3
TestFunc before yield 4
4
After input: 4
Cur input: 5
Output value: TestFunc after yield 4
TestFunc before yield 5
5
After input: 5

...

调用时发现,函数并没有一开始就执行,而是等到next时才开始执行。执行到co_yield就会自动暂停,下次next才会继续执行

另外,函数本身并没有返回什么,但是这里可以使用MyCoroGenerator<int>接收。函数的控制权也转移到了MyCoroGenerator<int>,需要执行时就调用next

函数在暂停后执行,依然可以继续上次的状态,这是因为,协程在挂起期间,其上下文全部都被保留,下次执行时恢复。

协程句柄

std::coroutine_handle类模板是实现协程的最底层的工具,负责存储协程的句柄。他可以特化为std::coroutine_handle<Promise>或者std::coroutine_handle<void>

这里面的Promise是实现协程必要的Promise类,而且其名字必须是promise_type

协程会暂停,其上下文也会保留,std::coroutine_handle就是保存和管理协程的地方。

std::coroutine_handle中方法不多,但是每个都很重要,其中:

  • done方法,可以查询协程是否已经结束
  • resume,恢复一个协程的执行
  • destroy,销毁一个协程

std::coroutine_handle是一个很底层的东西,没有RAII包裹,就像一个裸指针那样,需要手动创建、手动销毁。

所以需要一个包裹类,负责处理协程句柄的初始化和销毁,也就是Generator

Generator

C++的协程要求Generator必须有promise_type这个类,名字也必须这个,不能是其他的名字,直接定义在Generator中最方便。

c++ 复制代码
template <typename T>
struct MyCoroGenerator {
    struct promise_type {
        // todo
    };
};

promise_type中有一些固定接口,这些接口如果不实现,那么协程是不完整的。

  • initial_suspend,协程是否在初始化结束后挂起,返回std::suspend_always、std::suspend_never,这是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
  • final_suspend,协程最后一次执行是否挂起,返回值和上面函数相同。由于final_suspend是收尾阶段的工作,所以必须是noexcept
  • unhandled_exception,处理协程中未被处理的异常
  • get_return_object,返回一个Generator对象
  • yield_value,处理协程返回值,就是co_yield传递过来的值
  • return_void,处理协程结束后的返回值,和return_value同时只能存在一个,如果没有返回值就用return_void
  • return_value,处理协程结束后返回值,和return_void同时只能存在一个,如果有返回值就用return_value

解释一下yield_value函数,co_yield实际是一个语法糖,相当于co_wait promise.yield_value(i),只有实现了该函数,Genreator才能接收co_yield的返回值

函数的返回值,需要回答协程要不要挂起,也就是std::suspend_always或者std::suspend_never,因为需要yield后挂起,所以返回std::suspend_always

为了接收返回值,需要用转发和std::optional,接收并存储返回值。

get_return_object负责创建一个Generator,一般来说,使用std::coroutine_handle<promise_type>::from_promise接口从一个Promise创建句柄,然后使用这个句柄创建Generator。

为了配合创建函数,MyGenerator需要实现一个接收句柄的构造函数。

参考:C++ coroutine generator 实现笔记

相关推荐
_GR9 分钟前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
Death20027 分钟前
Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件
c语言·开发语言·c++·qt·c#
六点半88828 分钟前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法
coduck_S12004zbj1 小时前
csp-j模拟五补题报告
c++·算法·图论
Death2001 小时前
Qt 3D、QtQuick、QtQuick 3D 和 QML 的关系
c语言·c++·qt·3d·c#
sukalot1 小时前
windows C++-windows C++-使用任务和 XML HTTP 请求进行连接(二)
c++·windows
qianbo_insist2 小时前
simple c++ 无锁队列
开发语言·c++
zengy52 小时前
Effective C++中文版学习记录(三)
数据结构·c++·学习·stl
MinBadGuy2 小时前
【GeekBand】C++设计模式笔记5_Observer_观察者模式
c++·设计模式
QuantumStack5 小时前
【C++ 真题】B2037 奇偶数判断
数据结构·c++·算法