C++20 协程:从语法到实际应用

C++20 引入了协程(coroutines)这一强大的语言特性,为异步编程、生成器、延迟计算等场景提供了高效而简洁的实现方式。本文将从协程的基本概念、语法实现、编译器支持以及实际应用案例四个方面,系统梳理 C++20 协程的关键要点,并展示如何在现代项目中发挥协程的价值。

1. 协程概念回顾

协程是一种可挂起、可恢复的函数,能够在执行过程中暂停(yield)并在后续恢复,保持其内部状态。与传统线程相比,协程的切换成本极低(仅需保存/恢复栈帧指针等少量状态),适合大量并发任务的轻量级处理。C++20 通过对 co_awaitco_yieldco_return 三个关键字的引入,构成了协程的基本语法框架。

2. 关键字与语法细节

  • co_await:用于等待一个 awaitable 对象(如 future、promise 等)完成。
  • co_yield:在生成器中返回一个值,同时将协程挂起,等待下一个 co_awaitresume
  • co_return:结束协程,返回最终结果。
#include <coroutine>
#include <iostream>

struct generator {
    struct promise_type {
        int value_;
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(int v) { value_ = v; return {}; }
        void return_void() {}
        generator get_return_object() { return {this}; }
    };
    promise_type* promise_;
    generator(promise_type* p) : promise_(p) {}
    struct iterator {
        generator* gen_;
        bool operator++() { return gen_->promise_->initial_suspend().await_suspend(gen_->promise_); }
        int operator*() const { return gen_->promise_->value_; }
    };
    iterator begin() { return {this}; }
    void end() {}
};

generator range(int n) {
    for (int i = 0; i < n; ++i)
        co_yield i;
}

上述代码演示了一个简单的整数生成器,使用 co_yield 在每次迭代返回一个值。

3. Awaitable 对象设计

协程只能挂起 awaitable 对象。一个类型满足 awaitable 的条件是实现 await_readyawait_suspendawait_resume 三个成员函数。

struct timer {
    std::chrono::milliseconds dur_;
    bool await_ready() const noexcept { return dur_.count() == 0; }
    void await_suspend(std::coroutine_handle<> h) const {
        std::async(std::launch::async, [h, d = dur_]() {
            std::this_thread::sleep_for(d);
            h.resume();
        });
    }
    void await_resume() const noexcept {}
};

在上面的 timer 对象中,协程通过 co_await timer(1000ms) 实现毫秒级延迟。

4. 编译器与标准库支持

  • GCC 10+ / Clang 11+ / MSVC 19.26+:已完整实现协程基础。
  • **` `**:包含了 `std::coroutine_handle`、`std::suspend_always`、`std::suspend_never` 等工具。
  • <experimental/coroutine>:早期实现,已在标准库中合并。

在实际项目中,推荐使用 std::experimental::generator(C++23 预览)来简化生成器实现。

5. 实际应用场景

5.1 轻量级异步 IO

协程与 io_contextasio 等库结合,可以写出更直观的异步代码。

asio::awaitable <void> async_read(asio::ip::tcp::socket& sock) {
    char buf[1024];
    std::size_t n = co_await sock.async_read_some(asio::buffer(buf), asio::use_awaitable);
    std::cout << "Read " << n << " bytes\n";
}

5.2 并发任务调度

利用协程生成器实现工作池,避免线程上下文切换。

generator task_pool {
    for (auto& task : tasks)
        co_yield std::move(task);
}

5.3 实时游戏循环

在游戏引擎中,协程可用于实现 AI 行为树、动画序列等,保持代码可读性同时提升性能。

6. 性能与安全注意

  • 栈帧存储:协程默认将局部变量压入堆栈(或堆中)以保持挂起状态。
  • 异常传播:协程内部抛出的异常会被包装为 std::exception_ptr 并由 await_resume 重新抛出。
  • 资源管理:使用 std::unique_ptr 或自定义 RAII 包装器,确保协程挂起时资源安全。

7. 小结

C++20 协程为开发者提供了一种既高效又易用的异步编程模型。通过掌握 co_awaitco_yield 与 awaitable 的实现细节,结合标准库与第三方库的支持,能够在多种领域(网络编程、并发计算、游戏开发)中写出简洁、可维护且性能卓越的代码。随着 C++23 的到来,协程相关特性将进一步完善,期待更多生态工具为协程编程提供便利。

发表评论