C++ 23 中的协程:从原型到实践

在 C++ 23 版中,协程(coroutine)已经被正式纳入标准库,成为语言级别的特性。它们通过 co_awaitco_yieldco_return 等关键字让异步编程更像同步代码,减少回调地狱并提升可读性。本文从协程的基本概念出发,介绍其语法、实现原理以及在实际项目中的典型应用场景。


1. 协程的基本语法

#include <coroutine>
#include <iostream>

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

Task example() {
    std::cout << "Hello ";
    co_return;
}
  • promise_type:协程内部使用的承诺对象,用来管理协程的生命周期、异常传播和返回值。
  • initial_suspendfinal_suspend:分别决定协程起始和结束时是否挂起。std::suspend_never 表示不挂起,std::suspend_always 表示挂起。
  • co_returnco_yieldco_await:分别用于返回、产生值和等待异步操作。

2. 协程实现原理

协程本质上是一个可挂起的函数。编译器会把协程拆分为几个部分:

  1. 状态机:将函数体编译为状态机,使用内部 enum 或整数记录当前执行点。
  2. 挂起点co_awaitco_yieldco_return 所在位置,编译器插入挂起逻辑。
  3. 承诺对象(promise):承载协程的上下文,包括返回值、异常信息以及挂起/恢复的控制。

当协程被调用时,initial_suspend 决定是否立即执行,随后在第一次挂起点处暂停。随后调用方通过 resume()await_ready() 等函数手动恢复协程。


3. 常用协程包装器

  • std::generator(C++23):提供协程的生成器接口,支持 for (auto v : generator(...)) 迭代。

    std::generator <int> count_to(int n) {
        for (int i = 0; i <= n; ++i) co_yield i;
    }
  • std::task(C++23):表示一个可等待的任务,返回值可用 co_return 传递。

    std::task <int> async_add(int a, int b) {
        co_return a + b;
    }
  • 自定义协程:如 awaitablefuture 等,结合 std::experimental::coroutine_handle 进行手动控制。


4. 在实际项目中的应用

4.1 异步 I/O

使用 asiocppcoro 等库,将 co_await 与网络 I/O 结合,让异步操作像同步写法一样直观。

asio::awaitable <void> handle_client(tcp::socket socket) {
    std::array<char, 1024> buffer;
    std::size_t n = co_await socket.async_read_some(asio::buffer(buffer), asio::use_awaitable);
    co_await socket.async_write_some(asio::buffer(buffer, n), asio::use_awaitable);
}

4.2 并发任务调度

利用 std::task 与线程池配合,任务可以在不同线程间切换而不需要手动锁。

std::task <void> long_task() {
    for (int i = 0; i < 100; ++i) {
        std::cout << i << std::endl;
        co_await std::experimental::suspend_always{};
    }
}

4.3 响应式 UI

在 GUI 框架中使用协程实现事件驱动逻辑,避免回调嵌套。

std::task <void> on_button_click() {
    std::cout << "Button clicked!" << std::endl;
    co_await std::experimental::suspend_always{};
    std::cout << "Continuing after delay" << std::endl;
}

5. 性能与注意事项

  • 栈开销:协程的状态机存储在堆上或栈上,过度使用会导致堆碎片。可使用 std::experimental::coroutine_handle::from_promise 进行优化。
  • 异常传播promise_type::unhandled_exception 负责异常向上抛出,需确保异常安全。
  • 兼容性:C++ 23 标准已完善,但某些编译器在实现细节上仍有差异,建议在正式项目中使用稳定版(如 GCC 13+、Clang 15+、MSVC 19.35+)。

6. 结语

C++ 23 协程为异步编程提供了更自然、更高效的语义。借助标准库的 generatortask 等工具,开发者可以轻松构建高并发、低耦合的程序。未来,随着协程生态的成熟,预计会在网络、数据库、游戏、人工智能等领域得到更广泛应用。让我们拥抱协程,打造更具可维护性与性能的 C++ 代码吧!

发表评论