深入理解C++的协程:从概念到实践

C++20 在标准库中首次引入协程(coroutine)这一强大特性,为异步编程和高效的资源利用提供了全新的工具。本文将从协程的基本概念、实现机制、常见使用场景以及编写实践代码的细节,帮助你快速掌握这一功能。

一、协程的基本概念
协程是一种轻量级的函数,支持在执行过程中暂停(co_awaitco_yieldco_return)并在需要时恢复。与传统线程相比,协程没有自己的线程栈,线程上下文切换成本极低,适合大量并发任务。

二、实现原理

  1. 状态机化:编译器将协程函数转换为一个内部结构体(或类)并生成状态机。
  2. Promise对象:协程返回的 `std::coroutine_handle

    ` 与一个 `Promise` 对象关联,存放协程运行时的数据(返回值、异常、状态等)。

  3. 协程句柄std::coroutine_handle 用于手动控制协程(如 resumedestroy)。

三、常见协程类型

  • co_await:等待一个 awaitable 对象完成(例如异步 I/O)。
  • co_yield:在协程中生成序列值,类似生成器。
  • co_return:返回最终结果并结束协程。

四、典型使用场景

  1. 异步 I/O:结合网络库(如 Boost.Asio、libuv)实现非阻塞请求。
  2. 并行流处理:使用 co_yield 构造惰性序列,链式处理大量数据。
  3. 协程池:将协程封装为任务,统一调度执行。

五、示例代码

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

// 简单的 awaitable:模拟异步等待
struct SleepAwaitable {
    std::chrono::milliseconds ms;
    bool await_ready() const noexcept { return ms.count() == 0; }
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        std::thread([h, ms = ms]() {
            std::this_thread::sleep_for(ms);
            h.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

// 协程返回一个整数
struct IntTask {
    struct promise_type {
        int value_;
        IntTask get_return_object() {
            return IntTask{ std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_value(int v) { value_ = v; }
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> h_;
    IntTask(std::coroutine_handle <promise_type> h) : h_(h) {}
    ~IntTask() { if (h_) h_.destroy(); }
    int get() { h_.resume(); return h_.promise().value_; }
};

IntTask async_add(int a, int b) {
    // 模拟异步延迟
    co_await SleepAwaitable{ std::chrono::milliseconds(500) };
    co_return a + b;
}

int main() {
    std::cout << "启动异步计算...\n";
    auto task = async_add(10, 20);
    int result = task.get();  // 触发协程执行
    std::cout << "结果: " << result << '\n';
}

运行结果:

启动异步计算...
结果: 30

此例展示了如何用 co_await 实现异步延迟,co_return 返回结果,并通过 task.get() 触发协程执行。

六、常见陷阱

  • 异常处理:协程内抛出的异常会被传递到 promise_type::unhandled_exception,若未处理会终止程序。
  • 资源释放std::coroutine_handle 必须手动销毁,使用完后要调用 destroy() 或让对象析构。
  • 内存占用:虽然协程比线程轻量,但状态机仍在堆上分配,需注意内存管理。

七、进一步阅读

  • C++20 标准条文 30.11 协程
  • 《C++ Concurrency in Action》第二版(章节关于协程)
  • 现代异步 I/O 库:cppcoro、AsioCoroutines

结语
C++ 的协程为编写高并发、低延迟的异步代码提供了简洁而强大的语法。掌握其原理与使用技巧后,你可以在网络编程、游戏开发、数据处理等领域快速构建高性能应用。祝你编码愉快!

发表评论