时隔三个月,才回到当时说的学协程的坑,中间学了各种各样的东西,起码对现代 C++ 有些许了解了。尾递归优化 快速排序优化 CPS 变换 call/cc setjmp/longjmp coroutine 协程 栈编程和控制流 讲解_我说我谁呢 --CSDN博客

看这个文章之前可以先复习一下函数式编程里面的 call/cc 的概念,因为 C++20 的 stackless coroutine 就是用类似 call/cc 的手法实现的。

主要参考资料参照 Coroutines (C++20) - cppreference.com 这个。

为了学懂 coroutine 以及明白他的开销, 就不从 cppcoro 学协程了,而是从 c++ 20 的裸接口讲。


上面那篇讲了 call/cc 的笔记里面提到,协程是为了解决原来的并发编程里对于需要同步操作加锁来保证他 sequential 运行的而不是 parallel 产生 race condition。

协程和多线程编程的概念不是一个东西,协程和异步编程的概念则是不是一个概念。

多线程编程

下面是多线程编程例子,如果需要控制事情发生的某些先后顺序,那就要用阻塞和同步才能实现的效果,当然这个 block 的灰色部分可以去执行一些别的事情:


多线程异步编程 I

已经熟悉 C++11 的 std::future 和 std::promise 的人应该很熟悉多线程的异步编程了。用这两个东西并且用上多线程的时候,就涉及到阻塞和同步的问题,如果一个 future 还没有运行完,那么要获取他的唯一办法就是阻塞,等待他运行完为止。如果刚好的话就更好了,比如这里 notify 实际也可以是 thread 1 的非阻塞 polling,如果没有结果我继续干别的事情。


多线程异步编程 II

还有一个异步编程的样子,是有回调函数的多线程异步,他的一个逻辑不需要在同一个地方做完,所以不需要回到 thread 1.

这个对回调函数则有一些限制,回调函数是没有返回值的,他只是负责完成一些逻辑,比如 boost 的 asio 用来进行网络编程。但是异步编程的问题是写起来很麻烦,这个在 boost 的 asio 的文档里面讲 proactor 模式和 reactor 模式的时候都讲到了:

It is more difficult to develop applications using asynchronous mechanisms due to the separation in time and space between operation initiation and completion. Applications may also be harder to debug due to the inverted flow of control.

The Proactor Design Pattern: Concurrency Without Threads - 1.77.0 (boost.org)

比如下面这个 web server 的 accept 的例子(boost asio demo httpd),我们都知道异步 io 写一个服务器如果接收到一个连接就要把他加入连接池塘里,然后接收下一个请求的写法:这里一个回调里面还要做接下来的循环 accept 的逻辑,但是不能写循环,而是通过 tail callback 的方法实现,颇有回调地狱的味道!

void server::do_accept(){acceptor_.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {// Check whether the server was stopped by a signal before this// completion handler had a chance to run.if (!acceptor_.is_open())return;if (!ec) connection_manager_.start(std::make_shared<connection>(std::move(socket), connection_manager_, request_handler_));do_accept();});
}

协程

协程实际既可以单线程实现,也可以多线程实现,但是最重要的控制流的连续,重要的是并发,没有并行。前面讲的异步他都是 async 的意思,异步编程另外一个重要的关键字是 await,await 的好处就是可以同步地写异步程序。

要理解协程是什么,先从从 subroutine 来理解吧, 下面这个是一个 subroutine 的 callback 尾调用的控制流:

异步讲究的是一个回调,即回调必须发生在异步运行的事情的后面。回调就保证了这个单个线程上实现这个连续运行的效果。但是这种回调涉及了 3 个 subroutine,即你本来两个逻辑(蓝色和灰色)就应该用两个 routine 做的,但是必须要分层了 3 个 subroutine (即函数)来实现。

上面这个演示即 call/cc 这种回调风格,实际就是回调地狱,具体分析一下吧,这个写程序的时候要把一个逻辑分为两部分做,比如蓝色的下半部分,具体例子比如封到一个闭包(即一个带捕获的 lambda 表达式对象)里面,然后传给灰色部分,让他运行完他的东西之后调用我这个下半部分。

而协程的好处就是,在单线程的各个函数里增加一个 context switch 的语法糖,就能保住你原来只需要两个逻辑就能做的事情只用两个 coroutine 来实现,co 的意思就是通过协调来搞定同步的问题。对于上面这个的协程控制流是这样走的:

不过具体实现,可能还是要像回调地狱那样实现的(这个时候本来连函数的概念都没有,因为整个单核 CPU的并发本来就是单线程的进行一大堆 context switch 实现的,Program Counter 一直在前进和被修改而已,他并没有什么进程线程的概念),只能说这个实际是一个语法糖。

其实看到这里已经对怎么实现一个协程有一点思想实验了吧,特别是结合 call/cc 的那个魔幻调用以及 current continuation (即一个闭包)的概念。


熟悉 python 的肯定熟悉 generator,这里就通过一个 打印版的 generator 的示例来讲解 C++ 20 的 coroutine 的 await 是怎么实现的。

C++20 Coroutine 简介与协程的 accept 写法

实际 C++ 这个东西是很生的,只能说是提供了一个语法糖的实现框架,如果要像 C# 或者 js kotlin 那种需要配备一系列的库函数,比如 cppcoro 正在做的,不过这个进标准得等下个大版本了吧。为什么说生,下面看就知道了。

首先是基本的关键字,这一篇为了简化,所以只简介 co_await 。await 实际做的就是挂起当前的协程(一个函数),然后去执行另一个协程(一个可以等待的函数),co_yield 则是返回一个东西并且挂起。

比如说 a 是一个协程,可能要运行很久,写程序的时候就直接:await a; 或者 auto b = await a; 比如上面的 asio 的 httpd 写法就能改成这样写,瞬间清爽多了吧!

void server::do_accept(){for(;;){auto [ec, socket] = co_await acceptor_.async_accept();if (!acceptor_.is_open())return;if (!ec) connection_manager_.start(std::make_shared<connection>(std::move(socket), connection_manager_, request_handler_));}
}

下面就讲解这个 co_await 语法糖在 C++20 的编译器下要做些什么工作吧,顺便也能掌握编写这个协程的东西要做多么多的复杂工作。


用 C++20 写 generator

下面为了简化,我只写了个 print 的 generator,对于有返回值的 co_await 涉及的“复杂工作”更多,要解释的东西也更多了,下一篇再讲吧。

先看一下这个 generator 的使用样子,已经模仿了 python 了:

int main() {generator_counter count_from_24(24);for (int i = 0; i < 3; ++i) {count_from_24.next();}return 0;
}
// ----  结果是这些 :--
// 24
// 25
// 26

下面代码可以直接在 g++11 指定 -std=c++20 编译:

#include <concepts>
#include <coroutine>
#include <exception>
#include <iostream>// 由于本文不讲解返回值,所以这个时候就当这些是一些 convention 写法吧。
struct task_t {struct promise_type {task_t get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void unhandled_exception() {}};
};struct awaitable {std::coroutine_handle<>* anchor;constexpr bool await_ready() const noexcept { return false; }void await_suspend(std::coroutine_handle<> h) {if (anchor) {*anchor = h;anchor = nullptr;}}constexpr void await_resume() const noexcept {}
};task_t counter(std::coroutine_handle<>* continuation, int start) {awaitable a{continuation};for (unsigned i = start;; ++i) {co_await a;std::cout << i << std::endl;}
}struct generator_counter {int start;std::coroutine_handle<> closure_func;generator_counter(int begin) : start{begin}, closure_func{} {counter(&closure_func, start);}void next(){closure_func();}~generator_counter(){closure_func.destroy();}
};int main() {generator_counter count_from_24(24);for (int i = 0; i < 3; ++i) {count_from_24.next();}return 0;
}

下面就来解释这个东西和 call/cc 的关系。


有点累了,先休息一下,留个坑。

C++20 coroutine 探索I:co_await 原理 | 使用 C++ 协程写 python generator相关推荐

  1. 深入理解异步I/O+epoll+协程,附上epoll原理解析以及协程现实与原理剖析视频

    前言 同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行:而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会 ...

  2. C++20中的协程(Coroutine)

    目录 C++20中的协程(Coroutine) 那么,什么是协程? 那么这么好用的协程,是不是只要C++20一推出,我们加上一个关键字就能直接把异步调用转化为同步调用呢? 协程函数和Awaitable ...

  3. C++20 协程coroutine

    1.协程概念 协程函数与普通函数的区别: (1)普通函数执行完返回,则结束. 协程函数可以运行到一半,返回并保留上下文:下次唤醒时恢复上下文,可以接着执行. 协程与多线程: (1)协程适合IO密集型程 ...

  4. Unity协程(Coroutine)原理深入剖析再续

    Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...

  5. Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 记得去年6月份刚开始实习的时候,当时要我写网 ...

  6. unity3D协程(Coroutine)原理深入剖析

    原文:http://www.tuicool.com/articles/3IV32m Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:h ...

  7. 探索 Kotlin 协程原理

    接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解. 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上 ...

  8. Unity 协程(Coroutine)原理与用法详解

    前言: 协程在Unity中是一个很重要的概念,我们知道,在使用Unity进行游戏开发时,一般(注意是一般)不考虑多线程,那么如何处理一些在主任务之外的需求呢,Unity给我们提供了协程这种方式 为啥在 ...

  9. C++协程(二):Understanding operator co_await

    本文翻译自c++协程库cppcoro库作者Lewis Baker的github post,本篇为第二篇,原文内容在https://lewissbaker.github.io/2017/09/25/co ...

最新文章

  1. 基于自动驾驶车辆的激光雷达与摄像头之间的在线标定算法
  2. java 正则表达式提取价格
  3. openlayers入门开发系列之图层控制
  4. 哈密顿路径_检查图形是否为哈密顿量(哈密顿路径)
  5. ios无痕埋点_iOS无痕埋点方案分享探究
  6. 软件研发设计经验总结
  7. 两次DFS,POJ(1481)
  8. while 常见程序逻辑
  9. inner join去除重复_SQL多表查询:join表联结
  10. Pytorch中变量在tensor与numpy之间转换,tensor在CPU和GPU之间转换
  11. 听课记录高中计算机,高中听课记录
  12. 【css】表格单元格td元素设置最大高度
  13. MacBook外接显示器的显示模式介绍及设置说明(分屏/多屏)
  14. 针对芯片测试行业,常见stil,vcd,wgl,文件的转化,到93K,chroma,小总结
  15. 【芝麻背调百科】员工提交辞职信后又要求收回,公司能拒绝吗?
  16. 电商浪潮过后,无人零售会引领新零售的爆发吗
  17. VS2013,MFC,在视图类里添加鼠标左键响应函数OnLButtonDown
  18. 大数据和区块链区别和关系
  19. Google advertiser api开发概述——批量处理
  20. Golang实现发送微信公众号模板消息(每日一句和天气预报)

热门文章

  1. 线性代数(行列式矩阵向量)
  2. 《自学是门手艺》读后总结
  3. Java 基础语法(1)- 注释、标识符、关键字
  4. 关于Mobile Web App你所应该知道的
  5. 【正点原子MP157连载】第十六章 基本定时器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南
  6. android 采集cpu温度,阳光沙滩博客-Android开发如何读取到CPU,内存等硬件温度信息?...
  7. 关于android的sqlite问题,阳光沙滩-android studio的sqlite使用出现问题,是什么情况呢?...
  8. Compilation is not supported for following modules: example, image-viewer. Unfortunately you can't h
  9. 【GZ4-1-G24】哈威电磁阀
  10. 简易电子章制作小模块(VBA)