C++协程

发布于:2025-09-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

C++20引入了协程(Coroutines)特性,它是一种特殊的函数,可以在执行过程中暂停,并在后续某个时刻恢复执行,而不会像普通函数那样一旦返回就彻底结束。协程非常适合处理异步操作(如网络IO、文件IO)、生成器(产生序列值)、状态机等场景,能够以同步的代码风格编写异步逻辑,避免“回调地狱”。

一、协程的基本概念

与普通函数相比,协程有三个核心特性:

  • 可暂停:执行到特定点(co_awaitco_yield)时暂停,释放CPU控制权
  • 可恢复:暂停后可通过外部触发继续执行
  • 状态保留:暂停时的局部变量状态会被保存,恢复时继续使用

二、协程的识别与核心组件

1. 如何识别协程函数?

一个函数如果包含以下关键字之一,即为协程函数:

  • co_await:暂停等待某个操作完成
  • co_yield:产生一个值并暂停(类似迭代器的yield
  • co_return:返回结果并结束协程
2. 核心组件

C++协程的实现依赖三个关键组件(由编译器和用户/库共同管理):

  • 承诺对象(Promise Object):协程的“状态管理者”,负责创建返回值、处理暂停/恢复逻辑,由协程函数通过promise_type定义。
  • 协程句柄(std::coroutine_handle:用于外部控制协程(恢复、销毁)的轻量级指针。
  • 等待器(Awaiter)co_await后面的对象,定义了“等待”的行为(是否暂停、如何恢复)。

三、协程的生命周期

  1. 启动:调用协程函数时,编译器会:

    • 分配协程帧(存储局部变量、状态等)
    • 创建承诺对象(promise_type实例)
    • 执行协程函数体,直到遇到第一个暂停点(co_await/co_yield
  2. 暂停:遇到co_await expr时:

    • 计算expr得到一个等待器(Awaiter)
    • 调用等待器的await_suspend方法:若返回false则不暂停,继续执行;若返回true或协程句柄,则暂停,返回控制权给调用者。
  3. 恢复:通过协程句柄的resume()方法,协程从暂停点继续执行。

  4. 结束:执行到co_return或函数末尾时:

    • 调用承诺对象的return_value(或return_void)方法
    • 协程进入完成状态,可通过承诺对象获取结果

四、关键关键字详解

1. co_await:等待异步操作

co_await用于暂停协程,等待某个异步操作完成(如网络请求返回)。语法:

co_await awaitable; // awaitable是一个“可等待对象”(实现了等待器接口)

等待器(Awaiter)需要实现三个方法:

  • bool await_ready():返回true表示操作已完成,无需暂停
  • void await_suspend(std::coroutine_handle<> handle):暂停时调用,通常会保存协程句柄以便后续恢复
  • T await_resume():恢复时调用,返回等待结果(T为结果类型)
2. co_yield:产生序列值

co_yield用于生成一个值,然后暂停协程,类似迭代器的“产生-暂停”逻辑。语法:

co_yield value; // 等价于 co_await promise.yield_value(value)

常用于实现生成器(如无限序列、懒加载集合)。

3. co_return:返回结果并结束

co_return用于返回最终结果并终止协程,语法:

co_return expression; // 结果会通过承诺对象传递给调用者

五、示例:从简单到实用

1. 基础协程:返回一个值
#include <coroutine>
#include <iostream>
#include <stdexcept>

// 定义协程的返回类型(需要包含promise_type)
struct MyResult {
    // 承诺对象:协程内部状态管理者
    struct promise_type {
        int value; // 存储协程返回值
        
        // 协程启动时调用,返回MyResult对象
        MyResult get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        // 初始暂停:返回std::suspend_never表示不暂停,立即执行
        std::suspend_never initial_suspend() noexcept { return {}; }
        
        // 最终暂停:返回std::suspend_never表示协程结束后自动销毁
        std::suspend_never final_suspend() noexcept { return {}; }
        
        // 处理co_return:保存返回值
        void return_value(int v) { value = v; }
        
        // 处理异常
        void unhandled_exception() { std::terminate(); }
    };
    
    std::coroutine_handle<promise_type> handle; // 协程句柄
};

// 协程函数:返回MyResult
MyResult my_coroutine(int a, int b) {
    co_return a + b; // 使用co_return返回结果
}

int main() {
    auto result = my_coroutine(3, 5);
    std::cout << "Result: " << result.handle.promise().value << std::endl; // 输出8
    result.handle.destroy(); // 手动销毁协程帧(若final_suspend返回suspend_always)
    return 0;
}
2. 生成器:用co_yield产生序列
#include <coroutine>
#include <iostream>
#include <iterator>

// 生成器:产生int序列
struct Generator {
    struct promise_type {
        int current_value;
        
        Generator get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() noexcept { return {}; } // 初始暂停,等待resume
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
        
        // 处理co_yield:保存值并暂停
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {};
        }
    };
    
    // 实现迭代器接口,方便用for循环遍历
    struct iterator {
        std::coroutine_handle<promise_type> handle;
        
        bool operator!=(const iterator&) const { return !handle.done(); }
        void operator++() { handle.resume(); }
        int operator*() const { return handle.promise().current_value; }
    };
    
    iterator begin() { handle.resume(); return {handle}; }
    iterator end() { return {nullptr}; }
    
    std::coroutine_handle<promise_type> handle;
};

// 生成1~n的平方数
Generator squares(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i * i; // 产生平方数并暂停
    }
}

int main() {
    for (int v : squares(5)) { // 用for循环遍历生成器
        std::cout << v << " "; // 输出:1 4 9 16 25
    }
    return 0;
}
3. 异步操作:用co_await等待
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>

// 模拟异步IO操作的等待器
struct AsyncOperation {
    bool done = false;
    int result;
    
    // 检查操作是否完成(初始为false,需要暂停)
    bool await_ready() const noexcept { return done; }
    
    // 暂停时:启动异步操作,完成后恢复协程
    void await_suspend(std::coroutine_handle<> handle) {
        std::thread([this, handle]() {
            std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟IO耗时
            result = 42; // 异步操作结果
            done = true;
            handle.resume(); // 操作完成,恢复协程
        }).detach();
    }
    
    // 恢复时返回结果
    int await_resume() const noexcept { return result; }
};

// 协程函数:等待异步操作
struct AsyncResult {
    struct promise_type {
        int value;
        AsyncResult get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(int v) { value = v; }
        void unhandled_exception() { std::terminate(); }
    };
    std::coroutine_handle<promise_type> handle;
};

AsyncResult async_task() {
    std::cout << "等待异步操作..." << std::endl;
    int result = co_await AsyncOperation(); // 等待异步操作完成
    std::cout << "异步操作结果:" << result << std::endl; // 输出42
    co_return result;
}

int main() {
    auto task = async_task();
    // 等待异步操作完成(实际中可能需要更复杂的同步)
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

六、协程的优势与适用场景

优势:
  • 简化异步代码:以同步的线性风格编写异步逻辑,避免多层回调嵌套(“回调地狱”)。
  • 轻量级:协程暂停/恢复在用户态完成,无需操作系统介入,上下文切换成本远低于线程。
  • 状态保留:暂停时自动保存局部变量状态,无需手动管理(对比状态机的繁琐)。
适用场景:
  • 异步IO:网络请求、文件读写等IO密集型操作。
  • 生成器:懒加载序列(如无限数列、大数据流分页)。
  • 状态机:复杂流程的状态管理(如游戏AI、协议解析)。
  • 并发任务调度:在单线程内调度多个“逻辑线程”。

七、注意事项

  1. 标准库支持有限:C++标准仅定义了协程的基础机制(如std::coroutine_handle),具体异步操作(如网络、定时器)需要依赖第三方库(如libunifex、Boost.Coroutine2)或操作系统API封装。
  2. 内存管理:协程帧(存储状态的内存)需要手动管理(通过coroutine_handle::destroy()),否则可能内存泄漏。
  3. 调试复杂度:协程的暂停/恢复逻辑可能增加调试难度,需要支持协程的调试工具。
  4. 学习曲线:涉及承诺对象、等待器等概念,初期理解成本较高。

C++协程是现代C++异步编程的重要里程碑,虽然初期使用复杂,但能显著提升异步代码的可读性和效率。随着库生态的完善(如C++23/26可能增强标准库支持),协程将成为处理并发任务的主流方式。