C++20 中协程的实现原理与使用示例

C++20 引入了协程(coroutine)这一强大的异步编程工具,它使得我们可以以更直观、简洁的方式编写异步代码。下面从协程的底层实现原理、关键语法以及一个实际使用示例三个角度,帮助你快速上手。

1. 协程的实现原理

1.1 协程的基本概念

协程是一种能在执行过程中暂停并在之后恢复的函数。不同于线程,协程的上下文切换是由程序显式控制的,开销极低。C++ 协程的实现基于三大核心元素:

  • co_await:挂起协程,等待一个 awaitable 对象完成后恢复执行。
  • co_yield:在协程内部产生一个值,并暂停执行,等待外部再次激活。
  • co_return:返回协程的最终结果,结束协程。

1.2 生成器(generator)实现

C++20 中,标准库提供了 std::generator(在 <experimental/generator> 里)来实现生成器。其底层实现大致如下:

template<class T>
class generator {
public:
    struct promise_type {
        T current_value;
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T value) {
            current_value = std::move(value);
            return {};
        }
        void return_void() {}
        generator get_return_object() {
            return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
    };
    // ...
};
  • promise_type 保存协程状态,包括当前生成的值。
  • yield_value 负责把值存到 current_value 并暂停。
  • initial_suspendfinal_suspend 控制协程启动和结束时是否暂停。

1.3 协程的编译器支持

编译器在看到 co_awaitco_yieldco_return 时会自动生成以下结构:

  • Coroutine frame:在堆上分配一个结构体,保存局部变量、promise、状态机等。
  • State machine:把函数体拆分成若干状态段,通过 switch+label 实现暂停与恢复。
  • Coroutine handle:包装帧指针,提供 resume()destroy() 等操作。

因此,协程的运行成本相当于一个普通函数的递归调用,且无需多线程上下文切换。

2. C++20 协程核心语法

// 协程函数返回 std::generator <T>
std::generator <int> count_up_to(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i;          // 产生一个值并暂停
    }
}
  • co_yield:产生值后暂停,等待外部调用 resume()
  • co_await:对 awaitable 对象挂起,协程暂停;awaitable 必须提供 await_readyawait_suspendawait_resume
  • co_return:返回值(如果协程返回 std::generator,通常用 co_return 结束)。

协程对象可像普通迭代器使用:

for (int val : count_up_to(5)) {
    std::cout << val << ' '; // 输出 1 2 3 4 5
}

3. 实际使用示例:异步网络请求

下面给出一个利用协程实现异步 I/O 的简易示例(使用假设的 async 网络库)。

#include <coroutine>
#include <iostream>
#include <string>
#include <experimental/generator>

// 假设的 awaitable 类型,实际项目中会使用 asio、libuv 等
struct async_read {
    std::string data;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        // 异步操作开始,完成后调用 h.resume()
        std::thread([h, this]() {
            std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟 I/O
            data = "Hello from async!";
            h.resume();
        }).detach();
    }
    std::string await_resume() const noexcept { return data; }
};

std::coroutine_handle<> async_task();

std::generator<std::string> read_lines() {
    async_read ar;
    co_yield co_await ar; // 等待 async_read 完成
    std::istringstream iss(co_await ar); // 解析数据
    std::string line;
    while (std::getline(iss, line)) {
        co_yield line; // 每行返回一次
    }
}

int main() {
    for (auto&& line : read_lines()) {
        std::cout << "Line: " << line << '\n';
    }
}

说明:

  1. async_read 为自定义 awaitable,内部启动异步 I/O 线程。
  2. read_lines 协程先 co_await 读取数据,再按行 co_yield
  3. main 中像遍历容器一样消费协程产生的行,整个流程异步、非阻塞。

4. 小结

  • C++20 协程 通过 co_awaitco_yieldco_return 等关键字,简化了异步代码的写法。
  • 其实现基于编译器生成的状态机与 coroutine frame,开销低,易于理解。
  • 结合 awaitable 对象,协程可以实现网络 I/O、事件驱动、协作式多任务等多种场景。

掌握协程后,你将能写出更清晰、易维护且高效的异步程序,充分利用 C++20 的现代特性。祝编码愉快!

发表评论