现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

30 | Coroutines:协作式的交叉调度执行

co_return
co_yield
co_await
Coroutines introduction
Boost.Coroutine2
CppCoro
C++ extensions for coroutines
维基百科
无栈协程
有栈协程
MSVC
cppcoro
get
resume
promise_type
协程控制
协程的关键字
Coroutines TS
协程的用途
协程的基础
C++ 中的生成器
Python 中的生成器
协程的概念
参考资料
课后思考
内容小结
编译器支持
有栈协程和无栈协程
高层抽象
定义 uint64_resumable
C++20 协程
什么是协程?
协程 Coroutines

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
今天是我们未来篇的最后一讲,也是这个专栏正文内容的最后一篇了。我们讨论 C++20 里的又一个非常重要的新功能——协程 Coroutines。

什么是协程?

协程是一个很早就被提出的编程概念。根据高德纳的描述,协程的概念在 1958 年就被提出了。不过,它在主流编程语言中得到的支持不那么好,因而你很可能对它并不熟悉吧。
如果查阅维基百科,你可以看到下面这样的定义 [1]
协程是计算机程序的⼀类组件,推⼴了协作式多任务的⼦程序,允许执⾏被挂起与被恢复。相对⼦例程⽽⾔,协程更为⼀般和灵活……
等学完了这一讲,也许你可以明白这段话的意思。但对不了解协程的人来说,估计只能吐槽一句了,这是什么鬼?
图片源自网络
很遗憾,在 C++ 里的标准协程有点小复杂。我们还是从……Python 开始。
def fibonacci():
a = 0
b = 1
while True:
yield b
a, b = b, a + b
即使你没学过 Python,上面这个生成斐波那契数列的代码应该也不难理解。唯一看起来让人会觉得有点奇怪的应该就是那个 yield 了。这种写法在 Python 里叫做“生成器”(generator),返回的是一个可迭代的对象,每次迭代就能得到一个 yield 出来的结果。这就是一种很常见的协程形式了。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C++20引入了协程(Coroutines)这一重要的新功能,允许函数在执行过程中被挂起和恢复,为协作式多任务提供了支持。本文深入浅出地介绍了协程的概念和在C++20中的应用,对比展示了在C++和Python中实现斐波那契数列生成器的代码。文章详细讨论了C++20中协程的基础,包括协程的常见用途和新关键字,如`co_await`、`co_yield`和`co_return`。作者还展示了如何使用协程实现斐波那契数列生成器,并给出了调用代码的示例。此外,介绍了C++20协程的高层抽象,如cppcoro库提供的`generator`和MSVC的私有扩展。文章还讨论了有栈协程和无栈协程的区别,以及编译器对协程的支持。总的来说,本文全面介绍了协程的概念和在C++20中的应用,为读者提供了深入的了解和实践指导。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(11)

  • 最新
  • 精选
  • 吴咏炜
    置顶
    参考资料 [3] 的 cppcoro 目前看起来已经不再维护了,跟新版编译器的兼容性问题一直无人解决。目前比较好的复刻在下面这个链接: https://github.com/andreasbuhr/cppcoro
    2022-03-10
    2
  • 易轻尘
    个人对协程的理解,可能不太准确: 不可以混淆线程和协程两个概念。计算机看到的是线程,调度的也是线程。一个线程中可以有很多个协程,这些协程的执行顺序由程序员自己来调度。比较明显的好处是,1. 同一个线程中的协程不需要考虑数据的竞争问题,因为这些协程的执行顺序是固定的;2. 协程能够很方便的保存执行状态,使复杂状态机的实现变得简单;

    作者回复: 「计算机看到的是线程,调度的也是线程。」 把「计算机」改成「操作系统」,你的评论就没问题了。

    2020-06-28
    3
    7
  • Fireplusplus
    无栈协程的内存布局可以明白函数D调用结束后,应该是通过编译器处理后的方式返回到协程C的堆上空间,协程c挂起之后堆仍然是保留的,但是一个有栈协程的内存布局应该是什么样子的,协程挂起之后不是要出栈才能回到调用者的栈桢吗?

    作者回复: 无栈协程在进入协程时仍会使用原先的栈帧,所有的本地变量则固定分配在堆上的一块内存里。有栈协程在执行时会启用一个自己的独立栈帧,后面挂起和恢复执行时也都需要切换栈帧,因而开销比无栈的要大。 对具体细节感兴趣的话,网上这篇文章说得还比较细: https://mthli.xyz/stackful-stackless/

    2021-05-15
    3
  • 谦谦君子
    老师, 图左边“调用X的参数”在“返回Y的地址”下面, 而右边“调用X的参数”在“返回Y的地址”上面, 是画错了么, 还是协成里面就是跟栈上函数调用是反的呢?

    作者回复: 在栈里是一个明确的压栈顺序的(x86或类似平台上不管什么编译器实现都差不多)。而协程的数据放在堆里,并没有类似的顺序惯例,实际实现的顺序可能完全不一样。

    2020-02-12
    2
  • 李云龙
    老师下面的这个函数体内没有写co_return,为什么函数的返回值就可以写uint64_resumable ? uint64_resumable fibonacci() { uint64_t a = 0; uint64_t b = 1; while (true) { co_yield b; auto tmp = a; a = b; b += tmp; } }

    作者回复: 有co_的其中之一就是协程了。协程的第一次“返回”不是返回语句。

    2023-11-06归属地:北京
    1
  • englefly
    吴老师,请教两个问题: 1。coroutine的能力是不是比线程弱?我们可以用线程的 mutex+condition-variable 或 primise+future 模拟coroutine。但coroutine究竟哪些方面弱于线程呢? 2。coroutine的底层实现究竟是什么?它有cpu的上下文切换吗?还是所有coroutine都运行在同一个线程里,coroutine只是fibonacci c++实现那样的一种代码组织形式? 谢谢

    作者回复: 最基本协程和线程的区别就是,协程是程序自己调度的,线程是操作系统调度的。操作系统干的事情,开销自然大一些。反过来,协程的优势,性能就是很重要的一块了。 协程在哪个线程运行,是你的代码决定的。你从哪个线程去调用协程或去 co_await/co_yield 了,协程就在那个线程执行。像生成器那样的用法,完全只有一个线程,也不适合用多线程来改造。

    2020-03-29
    1
  • Gallen
    您好,吴老师,之前在2018年cpp开发者大会上听过您讲string view和range,还巧妙的使用|管道符进行函数间对象传递,能否有幸添加一下您的微信?谢谢

    作者回复: 有事情先发我邮件好了。应该不会找不到吧。

    2020-02-20
    2
    1
  • 晚风·和煦
    老师,map<int, int>().swap(map1); 这个语句为什么不能达到真正释放map1内存的效果呢?必须得用malloc_trim

    作者回复: 先说是不是,然后才谈得上为什么。 在大部分情况下都没有必要那么做。我从来没在代码里写过 malloc_trim。 在有虚拟内存的世界里,我看不出调用 malloc_trim 的必要性。操作系统自己能管好。 如果嵌入式开发,没有虚拟内存,你用 malloc_trim 也不见得有用。因为一旦堆的尾部有分配,你并不能释放内存回操作系统。 而且,你为什么要还内存给操作系统?以后你不用了吗?是程序要退出了吗?如果程序要退出,本来占用的资源就会被释放掉。如果程序不退出,进程管好自己的事,下次分配能不麻烦操作系统就不麻烦操作系统,应该反而更好。 我的个人见解,谁告诉你这句话的,基本上并不真懂应用开发,只是学了点 Linux 的知识,在瞎卖弄而已。

    2020-02-11
    1
  • Vackine
    感觉跟python里面的async的新的标准库好像,是真的有性能上的提升么,还是只是编程魔法?

    作者回复: 编程魔法和性能提升有矛盾么?🤪 严肃点,协程不是为了提高代码性能,而是为了提高程序员的生产率。从这点上来说,协程仍然是一种编译器的黑魔法。 跟手写比起来,没有性能的提升。就如同除了极少数的情况(比如泛型允许内联导致C++的排序比C的qsort快),C++代码不会比C代码性能更高。

    2020-02-10
    1
  • 很早了解过协程的概念,说是用户级线程,在用户态调度,避免线程的内核态切换,性能更高; 后来在一个 C++ 服务框架上也使用过,再后来切到 Go,用起了 goroutine; 简单来说,协程用起来是真爽,避免了恶心的异步 IO 的 callback 写法,自我感觉也算是理解并熟练使用协程了。 直到看了 C++ 20 的协程,完了惊掉下巴玩脑袋问号,这什么玩意儿,这是协程吗? 查各种资料和书,都跟以前理解的不一样,特别是看到有人所谓的用几行代码实现协程,那更是不知所以。 最后终于在这里解开了我的疑惑,原来以前理解和使用的协程,和 C++ 20 里的协程,一个是有栈协程,一个是无栈协程,有巨大差异,为啥在那些讲现代 C++ 的书里,都不提这个点,这个很关键呐,真是困扰我很久。

    作者回复: 现在著名的异步框架 Asio 也支持协程了。阿里也有个协程库 PhotonLib。小而美的可以研究一下 netcan/asyncio。

    2023-02-24归属地:广东
收起评论
显示
设置
留言
11
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部