**C++20 协程(Coroutines)到底是什么?它能解决哪些传统 C++ 中的难题?**

C++20 引入了协程(Coroutines)这一强大的语言特性,旨在简化异步编程、生成器和惰性序列的实现。与传统的回调、状态机或线程池相比,协程通过“挂起”和“恢复”的机制,让代码更像顺序执行,读写更直观。下面从概念、语法、实现细节以及常见应用场景几个角度,对 C++20 协程进行系统解析。


1. 协程的核心概念

1.1 协程(Coroutine)是什么?

协程是一种轻量级的子程序,它可以在执行过程中“挂起”(suspend)并在之后“恢复”(resume)。与线程不同,协程共享同一线程的栈空间,切换开销极低,适合需要频繁暂停/恢复的任务。

1.2 关键术语

术语 解释
co_await 等待一个 awaitable 对象,挂起协程直至该对象完成。
co_yield 在生成器中返回一个值并挂起协程,后续恢复时从此处继续。
co_return 结束协程并返回最终值。
Awaitable 拥有 operator co_await 的类型,或者可以直接转换为 std::suspend_always / std::suspend_never
Promise 协程体外部的状态对象,用来存储返回值、异常、以及协程状态。

2. 协程的语法与使用

2.1 简单的生成器(Generator)示例

#include <coroutine>
#include <iostream>
#include <vector>

template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        std::suspend_always return_void() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        Generator get_return_object() {
            return Generator{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        void unhandled_exception() { std::terminate(); }
    };

    using handle_type = std::coroutine_handle <promise_type>;

    handle_type coro_;
    explicit Generator(handle_type h) : coro_(h) {}
    ~Generator() { if (coro_) coro_.destroy(); }

    bool next() { return coro_.resume(); }
    T value() const { return coro_.promise().current_value; }
};

Generator <int> count_up_to(int limit) {
    for (int i = 0; i <= limit; ++i) {
        co_yield i;   // 生成一个值并挂起
    }
    co_return;        // 结束协程
}

int main() {
    auto gen = count_up_to(5);
    while (gen.next()) {
        std::cout << gen.value() << ' ';
    }
    std::cout << '\n';
}

输出:

0 1 2 3 4 5 

2.2 异步 I/O 协程示例

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

struct Task {
    struct promise_type {
        Task get_return_object() {
            return Task{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

Task async_sleep(std::chrono::milliseconds ms) {
    std::cout << "Start sleep for " << ms.count() << " ms\n";
    co_await std::suspend_always{};          // 模拟挂起
    std::this_thread::sleep_for(ms);        // 实际等待
    std::cout << "Done sleeping\n";
}

int main() {
    auto t = async_sleep(std::chrono::milliseconds(1000));
    t.coro_.resume(); // 立即运行到挂起点
    // 这里可以做其他事情
    t.coro_.resume(); // 恢复执行,完成睡眠
}

以上代码中,co_await std::suspend_always{} 仅作演示。实际项目中可结合 std::futureboost::asio 或自定义 awaitable 对象实现真正的异步 I/O。


3. 协程实现细节

3.1 promise_type 的作用

  • 存储协程内部状态(如当前值、异常等)。
  • 提供协程生命周期回调(initial_suspend, final_suspend 等)。
  • 与外部交互的接口(get_return_objectreturn_valueyield_value 等)。

3.2 handle_type 的使用

  • `std::coroutine_handle ` 用于手动控制协程的挂起、恢复和销毁。
  • 通过 resume() 恢复协程;done() 判断是否结束。

3.3 对 co_await 的扩展

自定义 awaitable 对象可以提供:

struct Awaitable {
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        // 例如将 h 放入事件循环的队列
    }
    int await_resume() const noexcept { return 42; }
};

co_await Awaitable{} 将导致协程挂起,直到外部触发 await_resume


4. 协程与传统方案的比较

方案 优点 缺点
回调 代码简洁 回调地狱,难以管理错误
状态机 可读性好 需要手动维护状态机代码
线程/线程池 并行执行 线程上下文切换成本高
协程 直观、轻量 需要 C++20 支持,学习曲线

4.1 典型使用场景

  1. 生成器(lazy sequences):如文件行读取、数值序列。
  2. 异步 I/O:网络请求、磁盘读写,配合 boost::asio 等框架。
  3. 协程化状态机:复杂事件驱动系统、游戏循环。
  4. 并发任务调度:将协程与线程池结合,实现高效并发。

5. 实践建议

  1. 从生成器开始:先实现简单的 Generator,熟悉 co_yieldpromise_type
  2. 逐步引入 awaitable:尝试实现自定义 Awaitable,并与异步事件循环结合。
  3. 使用库:如 cppcorofolly::corolibco 等已有协程库,避免重复造轮子。
  4. 性能评估:对比线程池、回调和协程在相同工作负载下的吞吐量与延迟。
  5. 异常安全:记得实现 unhandled_exception(),确保协程异常能正确传播。

6. 小结

C++20 的协程为语言注入了现代异步编程的便利,既保留了 C++ 的性能优势,又显著提升了代码可读性和维护性。通过掌握 promise_typeco_awaitco_yield 等核心概念,并结合 awaitable 对象,开发者可以轻松实现高效的生成器、异步 I/O 与协程化状态机。建议在实际项目中先从生成器入手,逐步扩展到更复杂的异步场景,以充分发挥协程的价值。

发表评论