C++20 中的协程到底是什么?

协程是 C++20 对“轻量级并发”的支持,旨在让异步编程更直观、更高效。它们可以在函数内部暂停执行、保存当前状态,并在之后恢复,避免了回调地狱与线程切换的开销。下面从概念、实现机制、关键语法以及典型使用场景等方面进行阐述。

一、协程的基本概念

  1. 暂停与恢复
    协程在执行过程中遇到 co_awaitco_yieldco_return 时会暂停。暂停点会保存所有局部状态(栈帧),之后可以随时恢复执行,直至再次暂停或结束。

  2. 协程对象
    每个协程都有一个对应的 promise 对象,用于维护协程的状态、返回值以及异常处理。协程函数返回一个 task 对象,该对象本身不是异步任务,而是一个 awaitable(可被 co_await 的对象)。

  3. Awaitable
    任意对象,只要实现 operator co_await() 并返回一个 Awaiter,便可被 co_await。C++20 标准库提供了 std::suspend_alwaysstd::suspend_never 等简易 Awaiter。

二、协程的实现原理

C++20 的协程编译器会把协程函数拆解成以下几部分:

  1. 生成器函数体
    变成一个普通函数,内部包含一个状态机。每次调用时会根据状态机的 label 继续执行到下一暂停点。

  2. 状态机
    采用 switch/case 结构保存执行位置。每个暂停点对应一个 case,在执行到暂停点前,程序会记录当前 label,以便下次恢复。

  3. 协程框架
    std::coroutine_handle 用来控制协程的生命周期。它提供 resume(), destroy(), done() 等接口。

三、关键语法与标准库支持

关键字/类型 作用 例子
co_await 暂停协程并等待 awaitable 完成 auto val = co_await fetch_async();
co_yield 产生一个值,暂停协程 co_yield i++;
co_return 结束协程并返回值 co_return result;
std::suspend_always / std::suspend_never 控制协程是否暂停 co_await std::suspend_always();
std::coroutine_handle 操作协程 `auto h = std::coroutine_handle
::from_promise(promise);`
std::experimental::generator 生成器类型(实验性) `std::experimental::generator
seq();`

简易协程示例

#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

Task async_sleep(int ms) {
    std::cout << "开始睡眠 " << ms << "ms\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
    co_return;
}

int main() {
    auto t = async_sleep(1000);
    t.get_return_object(); // 触发协程
    std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // 等待完成
}

四、典型使用场景

  1. 网络 I/O
    结合 asio 或自定义事件循环,协程可以直观地描述异步请求/响应流程,而不是嵌套回调。

  2. 异步生成器
    co_yield 用于实现流式数据生成器,例如遍历文件行、解析大数据流。

  3. 协作式多任务
    在单线程环境下,用协程实现多任务调度,减少线程上下文切换。

  4. 延迟计算
    对计算密集型任务使用 co_awaitstd::async 结合,实现懒加载与并行。

五、性能与注意事项

  • 协程对象占用
    协程的状态机存放在堆中,堆分配开销需要注意。可通过 co_await std::suspend_always() 等手段控制分配时机。

  • 异常传播
    若协程抛出异常,promise_type::unhandled_exception 会被调用。可自定义异常捕获策略。

  • 与标准库交互
    C++20 尚在标准化阶段,部分实现可能存在细微差异。建议使用 std::experimental::coroutine 或 Boost.Coroutine 进行跨编译器兼容。

六、总结

C++20 协程为异步编程提供了极致的语义和效率。通过暂停/恢复机制、Promise/Coroutine Handle 等抽象,程序员可以以同步代码的形式书写异步逻辑,显著提升代码可读性和维护性。未来随着标准库的完善与编译器优化,协程将成为 C++ 高性能异步编程的核心工具。

发表评论