在C++20中,协程(coroutine)被正式纳入标准库,成为处理异步操作的一把利器。协程的核心在于“挂起”和“恢复”,通过这些机制,程序员可以写出像同步代码一样易读、易维护的异步程序。下面从协程的语法、实现原理以及实际应用几个方面进行介绍。
1. 协程的基本语法
协程的核心语法是co_await、co_yield和co_return。一个协程函数返回的类型必须满足“协程返回类型”协议,一般使用std::future、std::generator或自定义的awaitable等。
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
struct Awaitable {
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
std::thread([h]{
std::this_thread::sleep_for(std::chrono::seconds(1));
h.resume();
}).detach();
}
int await_resume() const noexcept { return 42; }
};
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task example() {
std::cout << "Before await\n";
int value = co_await Awaitable{};
std::cout << "After await, value = " << value << "\n";
}
上述示例展示了一个简单的协程example(),它在co_await时挂起,等到Awaitable内部线程完成后再恢复。
2. 协程的实现原理
C++协程在编译期会被转换为一个状态机。关键点包括:
- 协程句柄(
std::coroutine_handle):持有协程的状态(栈帧、局部变量等),可以在挂起点恢复执行。 - 挂起点:
co_await、co_yield、co_return会生成不同的挂起点。await_ready()决定是否立即继续;await_suspend()在挂起时被调用,返回std::coroutine_handle可用于恢复。 - 生成器模式:
co_yield可以生成值序列,适用于惰性迭代。
编译器在生成协程时会对局部变量进行“保留”或“移动”,确保在挂起恢复时能恢复完整的状态。
3. 在异步编程中的应用
3.1 网络 I/O
使用协程配合异步 I/O 库(如Boost.Asio、libuv)可以让网络代码保持同步风格。
asio::awaitable <void> handle_client(tcp::socket sock) {
std::array<char, 1024> buffer;
std::size_t n = co_await sock.async_read_some(asio::buffer(buffer), asio::use_awaitable);
co_await sock.async_write_some(asio::buffer(buffer.data(), n), asio::use_awaitable);
}
这里async_read_some返回一个awaitable,协程在 I/O 完成前挂起,I/O 线程完成后恢复。
3.2 并行计算
协程可以与线程池结合,实现在主线程中异步等待计算结果。
std::future <int> heavy_computation() {
return std::async(std::launch::async, []{ /* 计算 */ return 123; });
}
asio::awaitable <void> main_task() {
int result = co_await std::move(heavy_computation()); // 在 async 线程完成后恢复
std::cout << "Result: " << result << "\n";
}
3.3 UI 与事件循环
在 GUI 应用中,协程可以替代回调链,简化事件处理逻辑。
4. 与传统异步模型的比较
| 方式 | 代码可读性 | 开销 | 灵活性 |
|---|---|---|---|
| 回调 | 低,容易产生“回调地狱” | 低 | 高 |
| Future/Promise | 中等,仍需链式操作 | 中 | 高 |
| 协程 | 高,接近同步写法 | 低到中 | 高 |
协程将同步代码的可读性与异步执行的性能优势相结合,是现代C++异步编程的主流选择。
5. 未来展望
C++23 将进一步完善协程功能,加入 std::generator、std::task 等更直观的协程返回类型,提升标准库对异步编程的支持。预计将有更多标准库组件(如文件 I/O、数据库访问)提供协程化接口,降低开发者的学习门槛。
小结
- C++20 的协程是处理异步任务的强大工具。
- 通过
co_await、co_yield、co_return让代码保持同步的可读性。 - 在网络、并行计算、UI 等领域已得到广泛应用。
- 随着 C++23 的发布,协程将进一步成熟,成为 C++ 开发者不可或缺的技能。