在 C++ 23 版中,协程(coroutine)已经被正式纳入标准库,成为语言级别的特性。它们通过 co_await、co_yield、co_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_suspend与final_suspend:分别决定协程起始和结束时是否挂起。std::suspend_never表示不挂起,std::suspend_always表示挂起。co_return、co_yield、co_await:分别用于返回、产生值和等待异步操作。
2. 协程实现原理
协程本质上是一个可挂起的函数。编译器会把协程拆分为几个部分:
- 状态机:将函数体编译为状态机,使用内部
enum或整数记录当前执行点。 - 挂起点:
co_await、co_yield、co_return所在位置,编译器插入挂起逻辑。 - 承诺对象(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; } -
自定义协程:如
awaitable、future等,结合std::experimental::coroutine_handle进行手动控制。
4. 在实际项目中的应用
4.1 异步 I/O
使用 asio 或 cppcoro 等库,将 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 协程为异步编程提供了更自然、更高效的语义。借助标准库的 generator、task 等工具,开发者可以轻松构建高并发、低耦合的程序。未来,随着协程生态的成熟,预计会在网络、数据库、游戏、人工智能等领域得到更广泛应用。让我们拥抱协程,打造更具可维护性与性能的 C++ 代码吧!