C++20 协程

  1. 协程 不是 C++函数, 而是一个 C++对象, 能 实现 挂起运行 和 恢复运行

    原理是, 协程 的 主函数 运行 所依赖的 栈(保存了所有的栈变量), 能 保存到堆 和 从堆重新恢复到栈

    当协程 挂起运行时, 就会 将所有的 栈变量 拷贝到 堆里

    当协程 恢复运行时, 再重新 将变量 从 堆拷贝到栈里

    整个过程 由 C++20 来支持(编译时? 运行时?)

    所有 的整个 运行过程 与 操作系统的线程 是完全 独立和无关的

  2. 当 一个 C++函数 中 出现了 co_await, co_yield 或 co_return 时

    就 成为了 一个 C++协程对象, 而不在是 C++函数了

  3. 关键字 co_await 后面 接 一个 "可等待对象"

    语义是: 让 当前函数(准确的说 已经不是函数了, 而是一个协程对象) "暂停并等待" 后面接的 这个 "可等待对象" "执行完成"

    "暂停并等待" 只是 阻塞的是 co_await 这条指令, 会导致 协程主函数 阻塞

    "可等待对象" 的 "执行完成" 是一个状态, 而不是 CPU指令流

    即 "可等待对象" 的状态 为 "ready" 或者 "suspend 后 resume"

    "可等待对象" 它用于 告知 协程 ( 即 co_await 的地方 ) 如何 暂停 和 恢复

    它 必须 实现 如下 三个 关键 函数

    await_ready():

    用于 告知 协程 能不能 拿到 "可等待对象" 的 返回结果

    如果 返回 false, 则 co_await(即源协程) 会执行到 await_suspend

    await_suspend(std::coroutine_handle<> handle):

    用于 执行 挂起后 的操作, co_await(即源协程) 会一直阻塞 等等其完成

    当然了, 这里也可以启线程, 如此 就不阻塞 co_await时 的 线程了

    但是 逻辑上, 整个协程 是 处于 "挂起" 状态的

    非常关键的是 参数 handle, 它 可以理解为 协程状态的遥控器

    不论 源线程 是否阻塞, 此时 的 协程肯定 是阻塞了(即被挂起了)

    如何恢复呢, 就需要调用 handle 的 resume 方法, 不论是在哪里(任何线程) 都能恢复 协程的执行

    额外 await_suspend 可以有 一个 bool 型返回值

    如果 返回 true, 则 认为是 正常的 挂起, 需要 将来 handle 的 resume 被调用 才行

    await_suspend 如果 是 void 与 "返回 true" 的行为类似

    如果 返回 false, 则 意味着 告诉 co_await(即源协程) 无需挂起, 直接恢复(有结果了)即可

    相当于 在 await_suspend 内就 调用了 handle 的 resume 方法, 只是效率更高了

    这个 bool 返回值的 用以 与 await_ready 的 返回值 正好 相反

    await_resume():

    当恢复时(即: 能 拿到结果时) 执行, 其返回值 就是 "可等待对象" 的 返回结果

  4. 协程(包含 co_* 的函数) 必须返回 一个 定义了 名称为 "promise_type" 的 "内部类" 的 类对象

    promise_type 的核心用以 是 实现 协程调用者 与 协程的 交互

    注: 这里的 "协程调用者" 可以是一个 普通的 C++ 函数

    当 外部函数 调用 协程 时, 第一时间 并不是 执行 协程的 主函数,

    而是 创建 一个 promise_type 对象

    然后 立刻调用 其 get_return_object 方法 构造一个 协程函数 的返回类型对象

    当 外部函数 需要 协程有 返回值时

    1. promise_type 需要 在 get_return_object() 函数里 构造 并返回 协程返回的 类型的对象

    这里 可以为 promise_type对象 和 "协程返回的 类型的对象" 建立一些联系

    1. 然后 需要在 协程的 主函数 里 直接 使用 co_return 返回 外部函数 需要的 数据对象(按需, 任何类型都行)

    当 co_return 被执行时, 会 触发 promise_type 的 return_value(...) 函数, 其参数 就是 co_return 返回的 对象

    1. 对于 外部函数(普通C++函数) 而言, 获取的返回值 仍然是 协程定义的 返回值类型 对象

    可以 在 这个 类型 上 定义 一个成员函数 或者 字段 能使 外部 函数 拿到 其想要的值

    1. 这个 值的 传递 是可以做到的, 因为

    2. 这个值 最初 极可能 是在 "可等待对象" 中产生的

    3. 在 "可等待对象" 的 await_resume 方法里 返回给了 协程

    即 co_await 执行后 拿到的 返回值

    1. 在 co_await 所在的 协程主函数里 使用 co_return 又能将 这个值 传递给 promise_type 的 return_value 这个函数的参数

    2. 而 promise_type 的 get_return_object 函数 在 构造 "整个协程 返回的 类对象" 时 有 建立 两者的联系

    此时 就又机会 将 整个返回值 传递给 "整个 协程的 返回值类型对象"

    1. 需要 注意 "协程返回类型的对象" 会在 协程主函数 执行前 就 被构造出来了

    此时 真正的 返回值(协程执行的成果) 肯定无法产生,

    所以 此时 只能先 建立 "协程返回类型的对象" 与 "promise_type对象" 的联系,

    "协程返回类型的对象" 是会给到 外部函数的,

    等待将来 能从

  5. 关于 initial_suspend

    首先 initial_suspend 的调用时机 是在 外部函数 调 用协程时 触发的 promise_type 构造函数 以及 get_return_object 执行完之后 所要立即 调用的 第三个 固定函数

    如果 其 返回 suspend_never 会让 协程体 立刻开始执行

    如果 其 返回 suspend_always 会让 协程立刻 挂起, 不立刻 执行 协程体

    此时 的 协程 将立刻 处于 挂起状态

    需要 某个地方 调用 coroutine_handle 的 resume 方法 才能 恢复 协程体的执行

    coroutine_handle 对象 除了 能在 Awaitable 的 await_suspend 中拿到外, 还能 随时由 promise_type 对象构造出来

    例如 可以在 get_return_object 函数里 构造 协程返回对象时 构造它, 并作为 参数 给到 协程返回对象

    std::coroutine_handle<promise_type>::from_promise(*this)

  6. 关于 final_suspend

    首先 final_suspend 的调用时机 时在 协程体 执行完 co_return 之后, 但在 协程对象 销毁前 被调用的

    如果 其 返回 suspend_never 则 协程 会立刻销毁, 释放资源

    如果 其 返回 suspend_always 则 协程不会销毁,

    但也不能再 调用 coroutine_handle 的 resume 了

    只能 调用 coroutine_handle 的 destroy 来触发 协程销毁

    用处, 主要是为了让 外部 感知协程结束, 并决定 其销毁时机

cpp 复制代码
#include<coroutine>
#include<iostream>
#include<memory>

struct Awaitable {
    bool await_ready() {
        return false;
    }
    void await_suspend(std::coroutine_handle<> handle) {
        // handle.resume();
    }
    int await_resume() {
        return 42;
    }
};
struct CoTask {
    struct promise_type {
        promise_type() {
            m_value = std::make_shared<int>();
        }
        CoTask get_return_object() {
            return CoTask(
                std::coroutine_handle<promise_type>::from_promise(*this),
                m_value
            );
        }
        std::suspend_always initial_suspend() {
            return std::suspend_always{};
        }
        std::suspend_always final_suspend() noexcept {
            return std::suspend_always{};
        }
        void unhandled_exception() {}
        // void return_void() {}
        void return_value(int v) {
            *m_value = v;
        }
        std::shared_ptr<int> m_value;
    };
    explicit CoTask(std::coroutine_handle<promise_type> handle, std::shared_ptr<int> value) {
        m_handle = handle; m_value = value;
    }
    std::coroutine_handle<promise_type> m_handle;
    std::shared_ptr<int> m_value;
};
CoTask run() {
    Awaitable aw;
    auto result = co_await aw;
    co_return result;
}
int main() {
    auto t = run();
    std::cout << *t.m_value << std::endl;
    for (;;) { auto c = getchar(); if (c == 'x') { break; }
        if (c == 'a') {
            t.m_handle.resume();
        }
    }
    t.m_handle.destroy();
    std::cout << *t.m_value << std::endl;
    return 0;
};
相关推荐
Zane1994122 小时前
CAS 与原子类:Java 如何实现无锁编程
java·开发语言
码农颜3 小时前
6.3 主函数流程剖析
开发语言·php
TheBestRucy4 小时前
Python 九阳神功:从零筑基到线程飞升
服务器·开发语言·网络·人工智能·python
研☆香4 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
小小龙学IT4 小时前
FlatBuffers 深度解析:Google 开源零拷贝序列化库完全指南
c++·开源
wuminyu4 小时前
JDK21中虚拟线程和FFM协同实现高并发源码剖析
java·linux·c语言·jvm·c++
草莓橙子碗4 小时前
Claude 使用指南
开发语言·python
SWAGGY..4 小时前
【C++进阶】:(1)继承机制详解
java·jvm·c++
longxiaozhang65 小时前
C# Array类:数组其实没那么难
开发语言·c#