**标题:C++20 协程(Coroutines)到底能帮我们做什么?**

C++20 新增的协程(Coroutines)是一项功能强大的语言特性,它为异步编程提供了更简洁、直观的语法。与传统的回调或 promise/async 方式相比,协程让“暂停”和“恢复”成为一种自然的程序流程。下面我们从概念、语法、实现原理以及实际案例等角度,对协程进行系统阐述,并给出几个实用的小技巧。


1. 协程的基本概念

协程是一种轻量级的函数,能够在执行过程中被“挂起”,随后在某个点继续执行。协程的核心是挂起点(suspend point),它可以是:

  • co_await:等待异步操作完成。
  • co_yield:产生一个值,挂起并返回给调用者。
  • co_return:结束协程并返回最终结果。

协程的执行状态(上下文)由编译器自动维护,开发者不需要手动管理线程或事件循环。


2. 关键字与语法

  • co_await:类似于 await,等待一个可等待对象。
  • co_yield:用于生成器模式,返回一个值后挂起。
  • co_return:结束协程并返回结果。
#include <coroutine>
#include <iostream>

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

Task async_print() {
    std::cout << "Before await\n";
    co_await std::suspend_always{}; // 这里挂起,模拟异步等待
    std::cout << "After await\n";
}

注意:协程的返回类型是一个可 promise_type 结构体的包装类。编译器会根据返回类型自动生成必要的 promise_type


3. 可等待对象(Awaitable)

任何可等待对象必须实现三个成员函数:

  1. await_ready()bool:是否立即完成。
  2. await_suspend(std::coroutine_handle<>)bool:挂起协程,返回是否挂起。
  3. await_resume()auto:协程恢复后返回值。
struct AsyncTimer {
    std::chrono::milliseconds delay;

    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) {
        std::thread([h, d=delay]() {
            std::this_thread::sleep_for(d);
            h.resume();            // 线程结束后恢复协程
        }).detach();
    }
    void await_resume() const noexcept {}
};

这样我们就能用 co_await AsyncTimer{500ms} 实现毫秒级异步等待。


4. 生成器模式

使用 co_yield 可以轻松实现惰性序列。

#include <coroutine>
#include <vector>

template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T v) {
            current_value = v; 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 return_void() {}
        void unhandled_exception() {}
    };

    std::coroutine_handle <promise_type> handle;

    explicit Generator(std::coroutine_handle <promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    struct Iterator {
        std::coroutine_handle <promise_type> h;
        Iterator(std::coroutine_handle <promise_type> h_) : h(h_) { if (h) h.resume(); }

        T operator*() const { return h.promise().current_value; }
        Iterator& operator++() { if (h) h.resume(); return *this; }
        bool operator!=(const Iterator& other) const { return h != other.h; }
    };

    Iterator begin() { return Iterator{handle}; }
    Iterator end() { return Iterator{nullptr}; }
};

Generator <int> count_to(int n) {
    for (int i=1; i<=n; ++i)
        co_yield i;
}

使用示例:

for (int x : count_to(5))
    std::cout << x << ' ';   // 输出 1 2 3 4 5

5. 典型应用场景

场景 传统实现 协程实现 优点
网络 I/O 回调 / async / promise co_await socket.read() 代码顺序化,错误处理更直观
并行流水线 线程/Task co_yield 产生结果流 更轻量,资源占用低
生成惰性序列 STL generator co_yield 与 STL 迭代器兼容,延迟评估
UI 事件循环 信号/槽 co_await 事件 事件驱动更自然,逻辑更清晰

6. 小技巧 & 常见坑

  1. 不要忘记 promise_typeinitial_suspend()
    默认返回 std::suspend_never,意味着协程在调用时即开始执行。若想立即挂起,需要返回 std::suspend_always

  2. co_yieldco_await 的区别
    co_yield 产生值并挂起,适合生成器;co_await 等待异步结果,挂起后再恢复。

  3. 线程安全
    协程本身是单线程执行的;如果在 await_suspend 中启动线程,请确保使用 std::coroutine_handle::resume() 时是线程安全的。

  4. 异常传播
    协程异常会传递到 promise_type::unhandled_exception(),默认实现会调用 std::terminate()。可自定义抛出自定义异常或记录错误。

  5. 协程对象的生命周期
    协程生成器在销毁时会自动销毁协程状态。若想在外部手动销毁,可调用 handle.destroy()


7. 结语

C++20 协程为现代 C++ 提供了一个统一且强大的异步编程模型。它将传统的异步代码抽象成可读、可维护的同步风格,同时保留了高性能和低开销的优势。掌握协程后,你可以轻松实现高性能网络服务器、异步数据库查询、数据流处理等复杂场景,而不再被回调地狱或事件循环所困扰。

祝你在 C++ 旅程中顺利利用协程,让代码更加简洁、优雅。

发表评论