C++20协程:从基本概念到实际应用

在 C++20 标准中,协程(Coroutines)被正式纳入语言层面,为异步编程提供了更直观、更高效的方式。本文将从协程的基本概念、语法细节、实现机制以及实际应用场景进行系统阐述,并给出完整代码示例。

1. 协程的基本概念

协程是一种轻量级的“挂起”和“恢复”函数。与线程不同,协程在同一线程中运行,切换成本极低,适合需要频繁切换执行状态的场景,例如网络 I/O、游戏循环、动画渲染等。

C++20 协程的核心在于 co_awaitco_yieldco_return 三个关键字,以及 generatortask 等模板封装。协程的生命周期由awaiter对象管理,awaiter 定义了何时挂起、何时恢复、何时结束。

2. 语法细节

2.1 基本协程函数

#include <coroutine>
#include <iostream>

struct Generator {
    struct promise_type {
        int current_value;
        std::suspend_always yield_value(int 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() {
        if (!handle.done()) handle.resume();
        return !handle.done();
    }
    int value() const { return handle.promise().current_value; }
};

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

2.2 Task(异步函数)

#include <coroutine>
#include <iostream>
#include <future>

template<typename T>
struct Task {
    struct promise_type {
        T value;
        std::exception_ptr eptr;
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        Task get_return_object() {
            return Task{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        void return_value(T v) { value = v; }
        void unhandled_exception() { eptr = std::current_exception(); }
    };

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

    T get() {
        if (handle.promise().eptr) std::rethrow_exception(handle.promise().eptr);
        return handle.promise().value;
    }
};

Task <int> async_add(int a, int b) {
    co_return a + b;
}

3. 协程的实现机制

C++20 协程本质上是对 state machine 的编译器支持。每个协程被编译成:

  1. promise_type:封装协程的状态、返回值、异常。
  2. handle:指向 promise 的 coroutine handle。
  3. awaiter:实现 co_awaitco_yield 的对象,决定挂起、恢复逻辑。

编译器负责把 co_yield 生成 yield_value 调用,将 co_return 生成 return_value 调用,并在生成器中插入 initial_suspendfinal_suspend 逻辑。

协程在 栈上 运行,只有 promise 对象在堆上。挂起时,调用者可以通过 std::coroutine_handle 控制恢复,保证切换成本几乎等同于普通函数调用。

4. 实际应用场景

4.1 异步 I/O

在网络编程中,使用协程可避免回调地狱。示例代码:

#include <asio.hpp>
#include <iostream>

asio::awaitable <void> read_socket(asio::ip::tcp::socket& sock) {
    std::array<char, 1024> buffer;
    while (true) {
        std::size_t n = co_await sock.async_read_some(asio::buffer(buffer), asio::use_awaitable);
        std::cout << "Received: " << std::string(buffer.data(), n) << '\n';
    }
}

4.2 GUI 事件循环

GUI 框架可以把 UI 更新包装成协程,使事件处理逻辑更直观。

async void animate() {
    for (int frame = 0; frame < 60; ++frame) {
        update_frame(frame);
        co_await std::this_thread::sleep_for(std::chrono::milliseconds(16));
    }
}

4.3 游戏引擎

协程用于实现脚本行为、状态机、路径规划等。其低切换成本使游戏逻辑更易维护。

5. 性能与注意事项

  • 挂起成本:协程切换成本低于线程切换,但仍比普通函数略高。应避免频繁 co_yield 在极高性能路径。
  • 资源管理:promise 位于堆上,需手动销毁或使用 std::unique_ptr。编译器会在 final_suspend 之后自动销毁。
  • 异常传播:协程异常需通过 promise_type::unhandled_exception 捕获,并在 get()rethrow_exception

6. 结语

C++20 协程为语言带来了异步编程的新语义,既保持了 C++ 的高性能特性,又提供了更直观的控制流模型。随着标准化和库的成熟,协程将在网络、图形、游戏、嵌入式等领域发挥越来越重要的作用。希望本文能帮助你快速上手协程,并在项目中灵活运用。

发表评论