C++20 新特性 协程 Coroutines(2)

目录
  • 1、co_await
  • 2、awaiter 的三个接口用途
  • 3、协程用法的回顾

想了解上一篇文章内容的小伙伴可点击 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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++20中的协程(Coroutine)的实现

    C++20中的协程(Coroutine) 从2017年开始, 协程(Coroutine)的概念就开始被建议加入C++20的标准中了,并已经开始有人对C++20协程的提案进行了介绍.1事实上,协程的概念在很早就出现了,甚至其他语言(JS,Python,C#等)早就已经支持了协程. 可见,协程并不是C++所特有的概念. 那么,什么是协程? 简单来说,协程就是一种特殊的函数,它可以在函数执行到某个地方的时候暂停执行,返回给调用者或恢复者(可以有一个返回值),并允许随后从暂停的地方恢复继续执行.注意,这

  • 详解c++20协程如何使用

    什么是协程 新接触的人看了网上很多人的见解都是一头雾水,本人的理解,协程就是可中断的函数,这个函数在执行到某一时刻可以暂停,保存当前的上下文(比如当前作用域的变量,函数参数等等),在后来某一时刻可以手动恢复这个中断的函数,把保存的上下文恢复并从中断的地方继续执行.简而言之,协程就是可中断的函数,协程如何实现:保存上下文和恢复上下文. 你可能会说协程不会这么简单的吧,我这里来举例一下啊,如python的协程 def test(): print('begin') yield print('hello

  • C++20 特性 协程 Coroutines(1)

    目录 一.协程简单介绍 二.协程的好处 三.协程得用法 四.协程三个关键字 五.协程工作原理 1.co_yield 2.co_return 我们先来介绍一下什么是协程. 一.协程简单介绍 协程和普通的函数 其实差不多. 不过这个 "函数" 能够暂停自己, 也能够被别人恢复. 普通的函数调用, 函数运行完返回一个值, 结束. 协程可以运行到一半, 返回一个值, 并且保留上下文. 下次恢复的时候还可以接着运行, 上下文 (比如局部变量) 都还在. 这就是最大的区别. 二.协程的好处 考虑多

  • C++20 新特性 协程 Coroutines(2)

    目录 1.co_await 2.awaiter 的三个接口用途 3.协程用法的回顾 想了解上一篇文章内容的小伙伴可点击 C++20 特性 协程 Coroutines (1) 谈到什么是协程. 并且介绍了 co_yield 和 co_return 的作用. 这篇来介绍一下 co_await. 1.co_await 一个形如: co_await awaitable 的表达式就叫一个 await-expression. co_await 表达式是用来暂停当前协程的运行, 转而等待 awaitable

  • C++通信新特性协程详细介绍

    目录 一.关于协程 二.协程的好处 三.协程得用法 四.与线程的区别 五.协程示例 一.关于协程 从 1.54.0 版本开始,Boost.Asio 支持协程.虽然您可以直接使用 Boost.Coroutine,但 Boost.Asio 中对协程的显式支持使得使用它们变得更加容易. 协程让您创建一个反映实际程序逻辑的结构.异步操作不会拆分函数,因为没有处理程序来定义异步操作完成时应该发生什么.程序可以使用顺序结构,而不是让处理程序相互调用. 二.协程的好处 考虑多任务协作的场景. 如果是线程的并发

  • 利用Kotlin的协程实现简单的异步加载详解

    前言 众所周知在android中当执行程序的耗时超过5秒时就会引发ANR而导致程序崩溃.由于UI的更新操作是在UI主线程进行的,理想状态下每秒展示60帧时人眼感受不到卡顿,1000ms/60帧,即每帧绘制时间不应超过16.67ms.如果某项操作的耗时超过这一数值就会导致UI卡顿.因此在实际的开发中我通常把耗时操作放在一个新的线程中(比如从网络获取数据,从SD卡读取图片等操作),但是呢在android中UI的更新只能在UI主线程中进行更新,因此当我们在非UI线程中执行某些操作的时候想要更新UI就需

  • 深入理解 Java、Kotlin、Go 的线程和协程

    前言 Go 语言比 Java 语言性能优越的一个原因,就是轻量级线程Goroutines(协程Coroutine).本篇文章深入分析下 Java 的线程和 Go 的协程. 协程是什么 协程并不是 Go 提出来的新概念,其他的一些编程语言,例如:Go.Python 等都可以在语言层面上实现协程,甚至是 Java,也可以通过使用扩展库来间接地支持协程. 当在网上搜索协程时,我们会看到: Kotlin 官方文档说「本质上,协程是轻量级的线程」. 很多博客提到「不需要从用户态切换到内核态」.「是协作式的

  • 使用kotlin协程提高app性能(译)

    协程是一种并发设计模式,您可以在Android上使用它来简化异步执行的代码.Kotlin1.3版本添加了 Coroutines,并基于其他语言的既定概念. 在Android上,协程有助于解决两个主要问题: 管理长时间运行的任务,否则可能会阻止主线程并导致应用冻结. 提供主安全性,或从主线程安全地调用网络或磁盘操作. 本主题描述了如何使用Kotlin协程解决这些问题,使您能够编写更清晰,更简洁的应用程序代码. 管理长时间运行的任务 在Android上,每个应用程序都有一个主线程来处理用户界面并管理

  • Kotlin协程概念原理与使用万字梳理

    目录 一.协程概述 1.概念 2.特点 3.原理 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 三.协程使用 1.协程的启动 2.协程间通信 3.多路复用 4.序列生成器 5.协程异步流 6.全局上下文 一.协程概述 1.概念 协程是Coroutine的中文简称,co表示协同.协作,routine表示程序.协程可以理解为多个互相协作的程序.协程是轻量级的线程,它的轻量体现在启动和切换,协程的启动不需要申请额外的堆栈空间:协程的切换发生在

  • Kotlin协程flowOn与线程切换超详细示例介绍

    目录 示例代码 一.flowOn方法 1.ChannelFlowOperatorImpl类 二.collect方法 1.ChannelFlowOperator类的collect方法 2.ChannelFlow类的collect方法 3.flow方法中代码的执行 4.接收flow方法发出的值 三.flowOn方法与流的融合 四.总结 示例代码 本文分析示例代码如下: launch(Dispatchers.Main) { flow { emit(1) emit(2) }.flowOn(Dispatc

  • kotlin 协程上下文异常处理详解

    目录 引言 一.协程上下文 1.CoroutineContext 2.CorountineScope 3.子协程继承父协程 二.协程的异常传递 1.协程的异常传播 2.不同上下文(没有继承关系)之间协程异常会怎么样? 3.向用户暴露异常 三.协程的异常处理 使用SupervisorJob 异常捕获器CoroutineExceptionHandler Android中全局异常的处理 引言 从前面我们可以大致了解了协程的玩法,如果一个协程中使用子协程,那么该协程会等待子协程执行结束后才真正退出,而达

随机推荐