C++ 20:协程(coroutine)简明入门

在 C++ 20 中,协程(coroutine)正式成为语言标准的一部分,为编写异步代码和实现协作式多任务提供了更自然、更高效的手段。相比传统的基于线程或回调的异步编程模型,协程能够在保持同步代码可读性的同时,显著减少上下文切换和资源消耗。本文将从概念、实现原理、关键关键词以及一个简单示例四个方面,带你快速入门 C++ 20 协程。

1. 协程的基本概念

协程是一种“轻量级线程”,其核心特性是挂起(suspend)与恢复(resume)。程序在某个点可以主动挂起执行,把控制权交还给调用者;随后,当需要继续执行时,协程从挂起点恢复,继续执行后续代码。协程的执行状态(局部变量、程序计数器等)会被保存在栈之外的内存中,等待下次恢复。

协程适用于:

  • 异步 I/O:在等待磁盘、网络等外部事件时挂起,避免阻塞线程。
  • 生成器:一次产生一个值,类似 Python 的生成器。
  • 并发任务:在多核环境下,协程可以在单线程中实现并行。

2. 协程的实现原理

C++ 协程的实现基于 C++ 20 标准库提供的 coroutine 关键字,底层实际上是生成器函数在编译时被拆解成一个 状态机。关键步骤如下:

  1. 定义协程函数:在函数签名前加 co_awaitco_yieldco_return 的关键字。
  2. 生成协程 promise:编译器为每个协程生成一个 promise_type,用于维护协程状态、返回值以及异常处理。
  3. 创建悬挂点co_awaitco_yield 会产生悬挂点,编译器把代码拆分成若干段,每段在协程挂起时停止执行。
  4. 调度恢复:当外部调用者或事件触发时,协程恢复执行,继续到下一个悬挂点。

C++ 标准库中并未提供具体的协程调度器,程序员需要自己实现或使用第三方库(如 cppcoroBoost.Asio 等)。

3. 关键关键词与类型

关键词 用途 示例
co_await 在协程中挂起,等待 awaitable 对象完成 int x = co_await async_read();
co_yield 在协程中生成一个值,类似生成器 co_yield i;
co_return 结束协程并返回值 co_return result;
std::future/std::promise 与协程配合使用的同步原语 auto fut = async_function();
co_initial_suspend/co_final_suspend 控制协程何时挂起与结束 return std::suspend_never{}

promise_type 结构

每个协程都有一个关联的 promise_type,典型成员:

struct my_coroutine_promise {
    int value;                     // 存储返回值
    std::exception_ptr eptr;       // 异常处理
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() { return {}; }
    void return_value(int v) { value = v; }
    void unhandled_exception() { eptr = std::current_exception(); }
};

4. 一个完整示例:异步文件读取

下面给出一个最简易的异步文件读取协程示例。由于标准库不包含 I/O 协程实现,此处演示协程的基本结构和 promise 机制。

#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>

// 1. 定义一个 awaitable 对象,模拟异步文件读取
struct async_file_reader {
    struct promise_type {
        std::string content;
        async_file_reader get_return_object() {
            return async_file_reader{ std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() { return {}; }
        void return_value(const std::string& c) { content = c; }
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> handle;
    async_file_reader(std::coroutine_handle <promise_type> h) : handle(h) {}
    std::string get_content() { return handle.promise().content; }
    void start() { handle.resume(); }
};

// 2. 协程函数,读取文件内容
async_file_reader read_file_async(const std::string& path) {
    // 模拟 I/O 延迟
    co_await std::suspend_always{};
    std::ifstream file(path);
    std::string data((std::istreambuf_iterator <char>(file)),
                     std::istreambuf_iterator <char>());
    co_return data;
}

// 3. 主函数
int main() {
    auto reader = read_file_async("example.txt");
    reader.start();          // 启动协程
    std::string content = reader.get_content();
    std::cout << "文件内容长度: " << content.size() << std::endl;
    return 0;
}

说明

  • async_file_reader 是一个 awaitable 类型,内部使用 promise_type 维护协程状态。
  • read_file_async 使用 co_await 挂起(这里仅演示,用 suspend_always 代替真正的异步等待),随后读取文件并 co_return 结果。
  • 主函数通过 reader.start() 开始协程执行,随后获取结果。

5. 小结

  • 协程让异步代码看起来像同步代码,显著提升可读性。
  • C++ 20 的协程通过 co_awaitco_yieldco_return 实现挂起与恢复,底层由 promise_type 管理状态。
  • 调度器不是标准的一部分,需自行实现或使用第三方库。
  • 虽然语法上简洁,但使用时仍需注意异常、安全与资源管理。

希望本篇文章能帮助你快速入门 C++ 20 协程,并在实际项目中尝试使用。祝编码愉快!

发表评论