# C++20协程(Coroutines)实战指南

在C++20中引入的协程(Coroutines)提供了一种更直观、更高效的方式来实现异步编程和惰性计算。相较于传统的回调、状态机或线程模型,协程让代码看起来像同步执行,但内部却隐藏了上下文切换、暂停与恢复的细节。本文将从基本概念入手,展示协程的核心语法,讲解典型用例,并给出实战建议与常见陷阱。

1. 协程基本概念

  • 挂起点(Suspension Point)co_awaitco_yieldco_return 处,协程暂停执行。
  • 恢复点(Resumption Point):在挂起点返回后,协程继续执行。
  • 协程句柄(Coroutine Handle)std::coroutine_handle<>,用于管理协程的生命周期。

2. 关键关键字

关键字 作用
co_await 暂停协程,等待异步操作完成后恢复
co_yield 生成惰性序列,每次返回一个值
co_return 结束协程并返回最终结果
co_return void 对无返回值的协程,直接结束

3. 典型实现示例

3.1 伪异步 I/O 示例

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

struct async_io {
    struct promise_type {
        async_io get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

async_io async_read(int duration_ms) {
    std::cout << "开始读取,等待 " << duration_ms << "ms\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
    co_return;
}

使用 co_await 调用:

async_io read_task = async_read(1000);
read_task.handle.resume(); // 手动恢复

3.2 惰性序列生成

#include <iostream>
#include <coroutine>
#include <optional>

template<typename T>
struct generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) {
            current_value = value;
            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::terminate(); }
    };

    std::coroutine_handle <promise_type> handle;

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

    bool next() { return handle.resume(); }

    T value() const { return handle.promise().current_value; }
};

generator <int> range(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i;
}

使用:

auto gen = range(0, 5);
while (gen.next()) {
    std::cout << gen.value() << " ";
}

输出:0 1 2 3 4

4. 与标准库的配合

  • std::future + std::async 已经被协程所补充。使用 std::experimental::future 或第三方库(如 cppcoro)可以实现更细粒度的协程化。
  • std::ranges 与协程结合,构建惰性管道,例如 std::views::filter + generator

5. 性能考量

  • 栈大小:协程默认使用线程栈,若需要更小栈可使用 std::experimental::coroutine_traits 结合自定义 promise_type
  • 挂起成本:每次 co_await 会产生上下文切换,但比线程切换更轻量。避免过度频繁的挂起。
  • 异常传播:协程内部的异常会在 co_await 处重新抛出。请使用 try/catch 进行错误处理。

6. 常见陷阱

  1. 忘记销毁句柄:协程句柄会占用资源,务必在使用后手动 destroy() 或让 RAII 自动释放。
  2. 协程对象复制:协程对象不支持复制,使用 std::move 或直接使用句柄传递。
  3. 不兼容的 awaitable:自定义 awaitable 必须满足 operator co_await 并返回支持 await_suspend 的对象。

7. 小结

C++20 协程提供了强大的异步与惰性计算能力,既保持了同步代码的可读性,又具备高效的执行特性。通过理解挂起点、恢复点与协程句柄的关系,并配合标准库的协程工具,开发者可以在网络编程、游戏循环以及大规模数据处理等场景中写出更简洁、高效的代码。欢迎你在实际项目中尝试,将协程与传统线程模型相结合,进一步提升系统性能与可维护性。

发表评论