协程(coroutine)是 C++20 标准引入的一种轻量级异步机制,它通过 co_await、co_yield 和 co_return 关键字,让函数能够在执行过程中暂停和恢复,而不需要手动管理线程或状态机。与传统的回调或 Promise 方式相比,协程写法更直观、易读,并且可以在编译期完成大部分检查,极大地提升开发效率。
1. 协程的基本语法
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
// 协程返回类型
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(); }
};
};
promise_type 用于控制协程的生命周期。initial_suspend 定义协程开始时是否挂起,final_suspend 定义结束时挂起。suspend_always 让协程在完成后挂起,允许外部代码通过 operator() 触发恢复。
2. 示例:异步计数
下面演示一个简单的异步计数器,模拟网络请求或 I/O 操作。
Task async_count(int n) {
for (int i = 1; i <= n; ++i) {
std::cout << "count: " << i << std::endl;
// 模拟异步等待
co_await std::suspend_always{};
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
调用方式:
int main() {
auto t = async_count(5);
for (int i = 0; i < 5; ++i) {
t(); // 手动恢复协程
}
return 0;
}
运行结果:
count: 1
count: 2
count: 3
count: 4
count: 5
3. 结合 std::future 与 std::async
C++20 允许协程返回 std::future,实现真正的异步任务。下面用 co_return 将结果包装进 std::future。
#include <future>
std::future <int> async_add(int a, int b) {
co_return a + b; // 结果直接返回到 future
}
调用:
int main() {
auto fut = async_add(3, 4);
std::cout << "Result: " << fut.get() << std::endl; // 阻塞等待结果
}
4. 处理异常
协程内部抛出的异常会被 promise_type::unhandled_exception 捕获。可以自定义处理逻辑:
struct promise_type {
// ...
void unhandled_exception() {
try {
std::rethrow_exception(std::current_exception());
} catch (const std::exception& e) {
std::cerr << "Coroutine exception: " << e.what() << std::endl;
}
}
};
5. 与第三方库协同使用
- Boost.Asio:利用协程实现异步 I/O 处理,代码更接近同步写法。
- cppcoro:提供更丰富的协程工具,如
generator,async_generator等。
6. 性能与注意事项
- 协程不需要额外线程,栈开销小;但若协程函数内部有大量栈变量,仍会占用线程栈。
- 在高并发场景下,协程的调度器设计决定性能;使用现成的事件循环(如
asio::io_context)可简化实现。 - 与旧标准代码兼容时,最好将协程封装在一个独立模块,逐步替换。
结语
C++ 协程让异步编程变得更加简洁、可维护。虽然学习曲线稍高,但随着编译器和标准库的完善,协程正逐步成为现代 C++ 开发不可或缺的工具。希望这篇文章能为你开启协程之旅提供参考。