掌握现代 C++ 异步编程的终极武器

在 C++20 正式引入协程(Coroutines)之前,异步编程长期依赖回调、Future/Promise 或第三方库(如 Boost.Asio、cppcoro),代码复杂、调试困难、可读性差。如今, 标准库为 C++ 带来了原生、高效、可组合的协程支持,彻底改变了异步与惰性计算的编写范式。

本文将从原理、语法、标准组件、自定义实现到工业级应用,全面剖析 C++  库,助你真正掌握这一现代 C++ 的核心异步机制。

一、什么是协程?为什么需要它?

1.1 协程 vs 线程

特性 线程(Thread) 协程(Coroutine)
调度 内核调度(抢占式) 用户态调度(协作式)
切换开销 高(上下文切换 + 缓存失效) 极低(仅保存寄存器/栈指针)
并发模型 多线程并发 单线程内高并发
同步需求 需互斥锁、条件变量等 无共享状态,天然无竞态

1.2 协程的核心能力

  • 挂起(suspend):暂停执行,保存状态,交出控制权
  • 恢复(resume):从挂起点继续执行
  • 值传递:可在挂起点返回值或接收参数

💡 协程不是并发模型,而是控制流抽象——它让异步代码“看起来像同步”。


二、C++20 协程基础语法

2.1 关键字三剑客

C++20 引入三个新关键字:

  • co_await:挂起并等待异步操作完成
  • co_yield:生成一个值并挂起(用于生成器)
  • co_return:结束协程并返回最终结果

2.2 协程函数的识别

一个函数只要包含上述任一关键字,即为协程函数。

但协程行为由返回类型决定——必须满足“协程接口约定”。


三、 标准库核心组件

#include <coroutine>

3.1 std::coroutine_handle

  • 作用:协程的“句柄”,用于手动控制生命周期
  • 关键方法
coroutine_handle<Promise> h = ...;h.resume();   // 恢复协程h.destroy();  // 销毁协程帧(需谨慎!)bool done = h.done(); // 是否已到达 co_return

3.2 std::noop_coroutine()

  • 返回一个空协程句柄,调用 resume() 无任何效果
  • 常用于初始化或占位

3.3 协程 Traits(编译期查询)

  • coroutine_traits<R, Args...>:推导协程的 Promise 类型
  • 通常由编译器自动使用,用户极少直接调用

⚠️ 注意:<coroutine> 不提供现成的协程类型(如 taskgenerator)!
它只提供底层构建块,具体协程类型需用户或库实现


四、协程工作原理:Promise 对象与协程帧

每个协程在编译时被转换为状态机,并关联一个Promise 对象。

Promise 控制协程的行为,其接口约定如下:

struct MyPromise {    // 必须提供以下成员(可自定义)    auto get_return_object();        // 返回协程函数的返回值类型    auto initial_suspend();          // 协程启动时是否挂起    auto final_suspend() noexcept;   // 协程结束时是否挂起    void return_void();              // co_return; 的处理(无返回值)    void return_value(T value);      // co_return value; 的处理    void unhandled_exception();      // 异常处理};

协程生命周期示例

task<int> foo() {    co_await delay(1s);    co_return 42;}

编译器将其转换为类似:

struct foo_promise : /* ... */ {    int result;    auto get_return_object() { return task<int>{coroutine_handle::from_promise(*this)}; }    auto initial_suspend() { return suspend_never{}; }    auto final_suspend() noexcept { return suspend_always{}; }    void return_value(int v) { result = v; }    // ...};

五、标准协程类型缺失?自己实现!

由于标准库未提供高层协程类型,我们以两个经典例子展示如何构建:

5.1 生成器(Generator)——惰性序列

#include <coroutine>#include <exception>template<typename T>class generator {public:    struct promise_type {        T value_;        std::exception_ptr ex_;        generator get_return_object() { return generator{handle::from_promise(*this)}; }        auto initial_suspend() { return std::suspend_always{}; }        auto final_suspend() noexcept { return std::suspend_always{}; }        void unhandled_exception() { ex_ = std::current_exception(); }        auto yield_value(T value) {            value_ = value;            return std::suspend_always{};        }        void return_void() {}    };    using handle = std::coroutine_handle<promise_type>;    explicit generator(handle h) : h_(h) {}    ~generator() { if (h_) h_.destroy(); }    generator(generator&& other) : h_(other.h_) { other.h_ = {}; }    generator& operator=(generator&& other) {        if (this != &other) {            if (h_) h_.destroy();            h_ = other.h_;            other.h_ = {};        }        return *this;    }    struct iterator {        handle h_;        T value_;        bool operator!=(std::default_sentinel_t) const { return !h_.done(); }        T operator*() const { return value_; }        void operator++() {            h_.resume();            if (h_.done()) return;            value_ = h_.promise().value_;        }    };    iterator begin() {        h_.resume(); // 启动协程        if (h_.done()) return {h_, {}};        return {h_, h_.promise().value_};    }    std::default_sentinel_t end() { return {}; }private:    handle h_;};// 使用示例generator<int> range(int n) {    for (int i = 0; i < n; ++i)        co_yield i;}// 调用for (int x : range(5)) {    std::cout << x << " "; // 输出: 0 1 2 3 4}

5.2 异步任务(Task)——无栈协程

template<typename T>class task {public:    struct promise_type {        T result_;        std::exception_ptr ex_;        std::coroutine_handle<> continuation_;        task get_return_object() { return task{handle::from_promise(*this)}; }        auto initial_suspend() { return std::suspend_always{}; }        auto final_suspend() noexcept {            struct awaiter {                promise_type& p;                bool await_ready() noexcept { return false; }                void await_suspend(std::coroutine_handle<> h) noexcept {                    if (p.continuation_) p.continuation_.resume();                }                void await_resume() noexcept {}            };            return awaiter{*this};        }        void return_value(T value) { result_ = value; }        void unhandled_exception() { ex_ = std::current_exception(); }    };    using handle = std::coroutine_handle<promise_type>;    bool await_ready() const { return false; }    void await_suspend(std::coroutine_handle<> h) {        h_.promise().continuation_ = h;        h_.resume(); // 启动任务    }    T await_resume() {        if (h_.promise().ex_) std::rethrow_exception(h_.promise().ex_);        return h_.promise().result_;    }private:    handle h_;};// 使用示例task<int> async_add(int a, int b) {    co_await delay(100ms); // 假设有异步延迟    co_return a + b;}task<void> main_task() {    int result = co_await async_add(2, 3);    std::cout << "Result: " << result << "\n"; // Result: 5}

六、Awaitable 对象:co_await 的魔法

任何对象只要满足Awaitable 接口,即可被 co_await:

struct MyAwaiter {    bool await_ready();          // 是否无需挂起    void await_suspend(handle);  // 挂起时调用(可启动异步操作)    T await_resume();            // 恢复时返回值};

示例:简单的定时器 Awaiter

struct timer_awaiter {    std::chrono::milliseconds ms;    bool await_ready() { return false; }    void await_suspend(std::coroutine_handle<> h) {        // 在后台线程 sleep 后 resume        std::thread([h, this]() {            std::this_thread::sleep_for(ms);            h.resume();        }).detach();    }    void await_resume() {}};timer_awaiter delay(std::chrono::milliseconds ms) {    return {ms};}// 使用task<void> example() {    co_await delay(1s); // 挂起1秒    std::cout << "Woke up!\n";}

七、性能与限制

7.1 性能优势

  • 零堆分配(若协程帧可栈分配)
  • 无上下文切换开销
  • 编译期优化友好

7.2 当前限制(C++20)

  • 无栈协程(Stackless):不能在嵌套函数中 co_await
  • 异常传播开销:需通过 promise.unhandled_exception() 手动处理
  • 调试困难:协程帧破坏调用栈,GDB 支持有限
  • 不可取消:标准未提供取消机制(需自行设计)

八、工业级实践建议

  1. 优先使用成熟库

    • cppcoro(Lewis Baker)

    • folly::coro(Facebook)

    • Boost.Async(未来可能标准化)

  2. 避免裸用 coroutine_handle:封装 RAII 管理生命周期

  3. 设计可组合的 Awaiter:如 when_allwhen_any

  4. 考虑取消与超时:通过 Token 或 Context 传递取消信号


九、未来展望:C++23 与 Beyond

  • std::generator:有望进入标准库(P2588R0)
  • 协程取消模型:标准化取消令牌(类似 C# CancellationToken)
  • 调试支持增强:编译器与调试器协同改进

结语

C++20 协程不是语法糖,而是一场异步编程范式的革命。虽然  本身只提供底层原语,但正是这种“最小化核心 + 可组合扩展”的设计,赋予了 C++ 无与伦比的灵活性与性能。

掌握协程,意味着你能:

  • 编写清晰如同步、高效如异步的代码
  • 构建高吞吐、低延迟的服务器/嵌入式系统
  • 探索惰性求值、管道处理、状态机等新范式

现在,是时候拥抱协程,开启 C++ 异步编程的新纪元了。

更多精彩推荐:

Android开发集

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选从 AIDL 到 HIDL:跨语言 Binder 通信的自动化桥接与零拷贝回调优化全栈指南

C/C++编程精选

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选宏之双刃剑:C/C++ 预处理器宏的威力、陷阱与现代化演进全解

开源工场与工具集

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选nlohmann/json:现代 C++ 开发者的 JSON 神器

MCU内核工坊

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选STM32:嵌入式世界的“瑞士军刀”——深度解析意法半导体32位MCU的架构演进、生态优势与全场景应用

拾光札记簿

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选周末遛娃好去处!黄河之巅畅享亲子欢乐时光

数智星河集

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选被算法盯上的岗位:人工智能优先取代的十大职业深度解析与人类突围路径

Docker 容器

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选Docker 原理及使用注意事项(精要版)

linux开发集

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选零拷贝之王:Linux splice() 全面深度解析与高性能实战指南

青衣染霜华

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选脑机接口:从瘫痪患者的“意念行走”到人类智能的下一次跃迁

QT开发记录-专栏

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选Qt 样式表(QSS)终极指南:打造媲美 Web 的精美原生界面

Web/webassembly技术情报局

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选WebAssembly 全栈透视:从应用开发到底层执行的完整技术链路与核心原理深度解析

数据库开发

青衣霜华渡白鸽,公众号:清荷雅集-墨染优选ARM Linux 下 SQLite3 数据库使用全方位指南

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐