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 实现笔记

相关推荐
??tobenewyorker1 小时前
力扣打卡第二十一天 中后遍历+中前遍历 构造二叉树
数据结构·c++·算法·leetcode
oioihoii2 小时前
C++11 forward_list 从基础到精通:原理、实践与性能优化
c++·性能优化·list
m0_687399842 小时前
写一个Ununtu C++ 程序,调用ffmpeg API, 来判断一个数字电影的视频文件mxf 是不是Jpeg2000?
开发语言·c++·ffmpeg
Ronin3053 小时前
【C++】类型转换
开发语言·c++
mrbone114 小时前
Git-git worktree的使用
开发语言·c++·git·cmake·worktree·gitab
虾球xz5 小时前
CppCon 2018 学习:EFFECTIVE REPLACEMENT OF DYNAMIC POLYMORPHISM WITH std::variant
开发语言·c++·学习
津津有味道6 小时前
Qt C++串口SerialPort通讯发送指令读写NFC M1卡
linux·c++·qt·串口通信·serial·m1·nfc
让我们一起加油好吗7 小时前
【C++】list 简介与模拟实现(详解)
开发语言·c++·visualstudio·stl·list
傅里叶的耶7 小时前
C++系列(二):告别低效循环!选择、循环、跳转原理与优化实战全解析
c++·visual studio
Vitta_U7 小时前
MFC的List Control自适应主界面大小
c++·list·mfc