C++20 中的协程:从概念到实践

协程(Coroutine)是 C++20 标准中一次性引入的重要特性,它为异步编程、生成器、并发等场景提供了更直观、更高效的实现方式。本文将从协程的基本概念、实现机制、典型用法以及与传统异步方式的比较,帮助读者快速掌握协程的使用技巧。

1. 协程的核心概念

1.1 什么是协程

协程是一种轻量级的用户级线程,它可以在执行过程中挂起(co_awaitco_yieldco_return)并在需要时恢复。与传统线程不同,协程的上下文切换成本极低,几乎是内存复制和指针更新。

1.2 协程的三大关键词

关键词 作用 典型用法
co_await 挂起协程,等待一个 awaitable 对象完成 int result = co_await asyncFunc();
co_yield 产生一个值并挂起协程,等待下一次拉取 co_yield i;
co_return 结束协程并返回最终值 co_return sum;

2. 协程实现机制

协程本质上是对函数的“编译后重写”。编译器会将协程函数拆分为若干个状态机(state machine),并在栈上保存必要的局部变量与执行点。每次执行到 co_* 关键词时,函数会将当前状态保存,并返回控制权;当外部再次调用时,协程会根据保存的状态继续执行。

编译器实现细节中最核心的是:

  • awaiter:实现了 await_ready(), await_suspend(), await_resume() 三个成员函数,用于描述 awaitable 对象的挂起与恢复。
  • promise:协程的返回值、异常传播与资源管理入口。协程函数的返回类型为 std::future 或自定义类型,内部包含 promise_type
  • handlestd::coroutine_handle 用于控制协程的生命周期(resume()destroy() 等)。

3. 典型协程用例

3.1 异步网络请求

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

struct async_wait {
    struct promise_type;
    using handle_t = std::coroutine_handle <promise_type>;

    struct promise_type {
        int value_;
        async_wait get_return_object() { return {handle_t::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_value(int v) { value_ = v; }
        void unhandled_exception() { std::terminate(); }
    };

    handle_t coro_;
    async_wait(handle_t h) : coro_(h) {}
    ~async_wait() { if (coro_) coro_.destroy(); }
    int get() { coro_.resume(); return coro_.promise().value_; }
};

async_wait async_sleep(int ms) {
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
    co_return ms;
}

int main() {
    int elapsed = async_sleep(500).get(); // 简化的同步等待
    std::cout << "耗时: " << elapsed << "ms\n";
}

这段代码演示了如何使用协程模拟异步等待。真实项目中,可以将 async_sleep 替换为异步 I/O 操作,协程在等待 I/O 时挂起,避免阻塞线程。

3.2 生成器(无限序列)

#include <coroutine>
#include <iostream>

template<typename T>
struct generator {
    struct promise_type;
    using handle_t = std::coroutine_handle <promise_type>;

    struct promise_type {
        T value_;
        generator get_return_object() { return {handle_t::from_promise(*this)}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T v) {
            value_ = v; return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    handle_t coro_;
    explicit generator(handle_t h) : coro_(h) {}
    ~generator() { if (coro_) coro_.destroy(); }

    T next() {
        coro_.resume();
        return coro_.promise().value_;
    }
};

generator <int> natural_numbers() {
    for (int i = 1; ; ++i)
        co_yield i;
}

int main() {
    auto gen = natural_numbers();
    for (int i = 0; i < 10; ++i)
        std::cout << gen.next() << " ";
}

这里演示了如何用协程实现一个生成器,支持无限序列。由于协程可以在 co_yield 时挂起,内存占用仅为当前值,效率极高。

3.3 异步管道(流)

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

struct async_pipe {
    struct promise_type;
    using handle_t = std::coroutine_handle <promise_type>;

    struct promise_type {
        std::vector <int> buffer_;
        async_pipe get_return_object() { return {handle_t::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(int v) {
            buffer_.push_back(v);
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    handle_t coro_;
    explicit async_pipe(handle_t h) : coro_(h) {}
    ~async_pipe() { if (coro_) coro_.destroy(); }

    const std::vector <int>& data() const { return coro_.promise().buffer_; }
};

async_pipe generate_data() {
    for (int i = 0; i < 5; ++i)
        co_yield i * 2;
}

int main() {
    auto pipe = generate_data();
    for (auto val : pipe.data())
        std::cout << val << " ";
}

协程可以作为生产者/消费者模式的核心,使得数据流的产生与消费解耦。

4. 协程与传统异步编程的比较

特性 传统回调 / Future 协程
可读性 回调嵌套导致“回调地狱” 代码顺序化,类似同步
错误处理 需要手动捕获、链式 propagate 统一异常传播 via promise
性能 每次回调都需要堆分配、线程切换 轻量级状态机,极低上下文切换成本
资源管理 需要显式释放 自动在 final_suspend 处销毁

5. 进一步学习资源

  • 《C++20协程实战》作者:刘鑫
  • cppreference.com 上的 std::coroutine_handlepromise_type 章节
  • GitHub 上的 “cppcoro” 项目,提供了更高级的协程工具库
  • 《Effective Modern C++》第二版中的协程章节

6. 结语

协程是 C++ 语言的一次重大演进,为异步编程提供了更自然、更高效的工具。掌握了协程后,你可以轻松实现异步网络、事件驱动、生成器、流式处理等多种模式,而不再需要依赖繁琐的回调或线程池。希望本文能帮助你快速入门并在实际项目中发挥协程的威力。

发表评论