C++20 新特性 协程 Coroutines(2)
想了解上一篇文章内容的小伙伴可点击 C++20 特性 协程 Coroutines (1)
谈到什么是协程. 并且介绍了 co_yield
和 co_return
的作用. 这篇来介绍一下 co_await
.
1、co_await
一个形如:
co_await awaitable
的表达式就叫一个 await-expression. co_await
表达式是用来暂停当前协程的运行, 转而等待 awaitable
的结果. 然后 awaitable 进行计算, 最终返回一个 awaiter
结构用来告诉 co_await
要做什么.
co_await
所在的函数块本身就是协程, 所以这个 co_await
也得配上一个 promise
和一个 coroutine_handle
. 就像上篇文章里面 generator
类之类的东西.
这个 awaitable
可以是很多东西, 首先会检查 promise 有没有提供 await_transform
函数, 如果有就会用上, 没有就不管.
(只要提供了任何一个 await_transform
, 那么每一个 awaitable 都需要找到适合它的重载, 否则就会报错. 库的实现者可以通过 await_transform 接口来限制哪些 awaitable 可以用在协程之中. 参见https://stackoverflow.com/q/65787797/14406396 )
之后的话, 会查找 operator co_await
这个函数, 预期这个 operator
返回一个 awaiter
.已经是一个 awaiter
了.
2、awaiter 的三个接口用途
一个 awaiter
需要实现三个接口 await_ready()
, await_suspend(std::coroutine_handle<P>) , await_resume() .
只要实现了这三个接口的东西就是 awaiter
.
await_ready()
告诉 co_await
自己好了没.
await_suspend(h)
可以选择返回 void , bool , std::coroutine_handle<P> 之一. h 是本协程的 handle. P是本协程的 promise 类型 (或者是 void, 见第三篇中的解释).
如果 await_ready()
返回 false , 这个协程就会暂停. 之后:
- 如果
await_suspend(h)
返回类型是 std::coroutine_handle<Z>, 那么就会恢复这个 handle. 即运行await_suspend(h).resume()
. 这意味着暂停本协程的时候, 可以恢复另一个协程. - 如果
await_suspend(h)
返回类型是 bool, 那么看 await_suspend(h) 的结果, 是 false 就恢复自己. - 如果
await_suspend(h)
返回类型是 void, 那么就直接执行. 执行完暂停本协程.
如果 await_ready()
返回 true 或者协程被恢复了, 那么就执行 await_resume()
, 它得到的结果就是最终结果.
所以说, 这await_ready
, await_suspend
, await_resume
三个接口分别表示 "有没有准备好", "停不停", "好了该咋办". 设计还是很自然的.
C++ 的协程是非对称协程, 是有一个调用/被调用的关系的. 一个协程被某个东西唤醒了, 那么它下次暂停的时候, 就会把控制流还给那个唤醒它的东西. 所以 C++ 的协程完全可以看作是一个可重入的函数.
3、协程用法的回顾
再来看上一篇文章中的伪代码
{ promise-type promise(promise-constructor-arguments); try { co_await promise.initial_suspend(); // 创建之后 第一次暂停 function-body // 函数体 } catch ( ... ) { if (!initial-await-resume-called) throw; promise.unhandled_exception(); } final-suspend: co_await promise.final_suspend(); // 最后一次暂停 }
catch
块里面出现的 !initial-await-resume-called
就是指 promise.initial_suspend()
返回的那个 await_resume()
有没有被执行过.
如果执行了, 那么这个 flag 就会立刻变成 true. 然后调用 promise.unhandled_exception() 来处理异常.
一个例子:
由于 co_await
对这三个东西的应该做什么没有做任何限制, 所以可以用来实现很多功能.
举个例子 (来自标准库), 比如我们想要设计一个协程, 能够停下任意的正时长, 就可以这样设计:
template <class Rep, class Period> auto operator co_await(std::chrono::duration<Rep, Period> d) // operator co_await { struct awaiter { std::chrono::system_clock::duration duration; awaiter(std::chrono::system_clock::duration d) : duration(d) {} bool await_ready() const { return duration.count() <= 0; } int await_resume() { return 1; } void await_suspend(std::coroutine_handle<> h) { std::this_thread::sleep_for(duration); } }; return awaiter{d}; }
这样的话, 如果输入一个正的时间, 就会调用 await_suspend()
进行暂停了. 如果输入的时间是负的, 那就通过 await_ready()
返回 true 绕过了这个过程.
当然, 调用它需要在一个协程中, 也就意味着需要一个 promise
和 coroutine_handle
包装类的配合. 像这样
struct my_future { struct promise_type; using handle = std::coroutine_handle<promise_type>; struct promise_type { int current_value; auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() { return std::suspend_always{}; } void unhandled_exception() { std::terminate(); } /* ... */ }; /* ... */ private: my_future(handle h) : coro(h) {} handle coro; }; my_future sleep_coro() { printf("Start sleeping\n"); int ans = co_await 1s; printf("End sleeping, with ans = %d\n", ans); }
当然, 一个函数也可以放在 co_await
的右边, 就像 co_await g();
只要返回的结构里面有那三个 await_* 接口就行. 甚至你可以直接 co_await std::suspend_always{};
下面是协程流控的细致分析.
int main() { auto h = sleep_coro(); // 这一步创建协程, 在 co_await initial_suspend 处, 执行完 await_ready, await_suspend. 返回 main // 注意 initial_suspend 返回的是 std::suspend_always{} // 所以是一定暂停, 并且 resume 的时候什么都不做 h.resume(); // 这一步执行上一个 await_resume 以后(什么都不做), 执行了 printf("Start sleeping\n"); // 然后收到 co_await 1s 返回的结构, 其中 await_suspend 里面需要暂停. // 然后执行完 await_ready, await_suspend (在这个函数里暂停 1s), 返回 main h.resume(); // 这一步执行完 await_resume 以后(初始化 ans = 1) // 执行了 printf("End sleeping, with ans = %d\n", ans); // 然后在 co_await final_suspend 处执行完 await_ready, await_suspend. 就返回 main }
示列代码见这里
到这里大家可以重新会到(1)去看看:C++20 特性 协程 Coroutines(1)
到此这篇关于C++20 新特性 协程 Coroutines(2)的文章就介绍到这了,更多相关C++20 协程 Coroutines
内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
Android kotlin+协程+Room数据库的简单使用
这篇文章主要介绍了Android kotlin+协程+Room数据库的简单使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-04python实习总结(yeild,async,azwait和协程)
今天是Python实习的第一天,熟悉了环境,第一次使用macbook,氛围还不错,努力学习新知识,希望本片文章能给你带来帮助...2021-10-08- 这篇文章主要给大家分享得是C++20 得特性 协程 Coroutines,下面文章内容我们将来具体介绍什么是协程,协程得好处等知识点,需要的朋友可以参考一下...2021-10-08
- 在本篇内容里小编给大家总结的是一篇关于python中Task封装协程的知识点总结内容,有兴趣的朋友们可以跟着学习下。...2021-07-21
- 这篇文章主要介绍了C#迭代器及Unity协程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了python 多进程和协程配合使用写入数据,帮助大家利用python高效办公,感兴趣的朋友可以了解下...2020-10-30
- 上篇文章简单给大介绍了 C++20 特性 协程 Coroutines co_yield 和 co_return 那么这篇文章继续给大家介绍C++20 的新特性协程 Coroutines co_await,需要的朋友可以参考一下...2021-10-08
- 在本篇文章里小编给大家整理的是一篇关于python在协程中增加任务实例操作内容,有兴趣的朋友们可以学习下。...2021-02-28
- 这篇文章主要介绍了python已协程方式处理任务实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-09
- 这篇文章主要介绍了Java 实现协程的方法,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下...2020-10-11
- 这篇文章主要介绍了Go并发:使用sync.WaitGroup实现协程同步方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-05-04
- 这篇文章主要介绍了解决go在函数退出后子协程的退出问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-05-01
- 协程又称为微线程,协程是一种用户态的轻量级线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程...2021-06-16
- 这篇文章主要介绍了python中asyncio异步编程学习,内部就是基于协程实现的异步编程,如果想研究异步编程的同学,要仔细看哦...2021-04-06
- 这篇文章主要介绍了python asyncio 协程库的使用,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下...2021-01-22
- 这篇文章主要介绍了C语言中实现协程案例,本文通过将协程与线程和异步回调进行对比,以及具体实现案例,以下就是详细内容,需要的朋友可以参考下...2021-07-03
- 这篇文章主要介绍了python使用协程实现并发操作的方法,结合实例形式详细分析了Python协程的原理及使用Gevent实现协程操作的相关技巧与操作注意事项,需要的朋友可以参考下...2020-05-09
- 协程是互相协作的程序,协程是结构化的。如果把Java的异常处理机制,照搬到Kotlin协程中,一定会遇到很多的坑。Kotlin协程中的异常主要分两大类,协程取消异常(CancellationException)其他异常...2022-08-27
- 这篇文章主要介绍了Kotlin创建一个好用的协程作用域,kotlin中使用协程,是一定要跟协程作用域一起配合使用的,否则可能协程的生命周期无法被准确控制,造成内存泄漏或其他问题...2022-07-21
- 这篇文章主要为大家详细介绍了Python进程,线程和协程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助...2022-03-10