在C++20标准中,协程(coroutine)被正式引入,成为一种轻量级的、基于协作式多任务的异步编程工具。它们让代码更直观地描述异步流程,避免回调地狱和状态机的繁琐。本文将从协程的基础概念、关键语法、实现机制,到实战应用逐步展开,帮助你快速上手并将其融入日常项目。
1. 协程的基本概念
协程是一种可暂停和恢复的函数,它在运行时可以在任意点挂起(yield)并保存自己的执行状态,随后在需要时继续执行。与传统线程不同,协程不需要操作系统调度,所有挂起和恢复都在单线程内完成,因而开销更小。
1.1 协程与异步I/O的关系
协程可以与异步I/O配合使用,利用事件循环(Event Loop)或操作系统的I/O复用机制(如epoll、IOCP)实现非阻塞I/O。协程的挂起点会在I/O完成前暂停,避免线程阻塞,提升系统并发性能。
1.2 关键特性
- 协作式多任务:协程由程序显式挂起/恢复,完全由代码控制。
- 状态保存:协程在挂起时自动保存局部变量状态。
- 可组合性:协程可以互相调用,形成链式调用或并行执行。
- 类型安全:协程返回类型是
std::coroutine_handle或自定义返回类型。
2. C++20协程的核心语法
2.1 co_await、co_yield、co_return
co_await:等待一个 awaitable 对象完成,并在完成后返回其结果。若 awaitable 为空,协程立即继续。co_yield:返回一个值给调用者,同时挂起协程。适用于生成器(generator)模式。co_return:终止协程并返回值,若无返回值,则使用void。
2.2 std::suspend_always 与 std::suspend_never
这两个辅助结构决定协程在启动和结束时是否立即挂起。常用于自定义协程框架。
2.3 示例:简单的异步计时器
#include <chrono>
#include <coroutine>
#include <iostream>
struct timer {
struct promise_type {
timer 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(); }
};
};
timer sleep_for(std::chrono::milliseconds ms) {
struct sleep_awaiter {
std::chrono::milliseconds dur;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([h, dur=dur]{
std::this_thread::sleep_for(dur);
h.resume();
}).detach();
}
void await_resume() {}
};
co_await sleep_awaiter{ms};
}
此代码演示了如何创建一个异步计时器。await_suspend 在新线程中完成 sleep,随后通过协程句柄恢复。
3. 生成器(Generator)的实现
生成器是一种特殊的协程,用于按需产生一系列值。C++20 并未直接提供标准库实现,但可以利用 co_yield 自行实现。
3.1 生成器的返回类型
template<typename T>
struct generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T 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;
generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~generator() { if (handle) handle.destroy(); }
T next() {
handle.resume();
return handle.promise().current_value;
}
bool has_next() const { return !handle.done(); }
};
3.2 使用示例
generator <int> count_to(int n) {
for (int i = 0; i < n; ++i)
co_yield i;
}
int main() {
for (auto g = count_to(5); g.has_next(); ) {
std::cout << g.next() << ' ';
}
// 输出: 0 1 2 3 4
}
4. 实战:协程与网络I/O
下面给出一个使用协程的异步 TCP 服务器示例,基于 Boost.Asio(支持协程接口)。
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::asio::experimental::awaitable;
using namespace std::chrono_literals;
awaitable <void> handle_session(tcp::socket socket) {
char data[1024];
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
co_await boost::asio::async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
}
awaitable <void> server(unsigned short port) {
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port));
for (;;) {
tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable);
std::jthread t([socket = std::move(socket)]() mutable {
co_spawn(io_context, handle_session(std::move(socket)), boost::asio::detached);
});
}
}
int main() {
boost::asio::co_spawn(
boost::asio::io_context{},
server(12345),
boost::asio::detached);
return 0;
}
此示例展示了如何利用 Boost.Asio 的 async_* 与 awaitable 结合,使异步网络代码更直观。
5. 性能与注意事项
| 方面 | 说明 |
|---|---|
| 堆栈占用 | 协程不需要完整堆栈,状态由编译器生成;但递归协程会产生更大状态体。 |
| 异常安全 | 需要在 promise_type 中实现 unhandled_exception;协程挂起点附近的异常会传递给外层。 |
| 调试 | 调试器对协程的断点支持逐步改进,但在复杂链式协程中仍可能出现跳转。 |
| 兼容性 | 需要编译器支持 C++20 协程(GCC ≥10、Clang ≥11、MSVC 19.26)。 |
6. 未来展望
- 协程池:类似线程池的协程池可进一步降低协程切换成本。
- 多阶段协程:通过自定义
awaitable实现分阶段任务,例如预处理、IO、后处理。 - 协程与GCD:在iOS/macOS中,协程可以与 Grand Central Dispatch 整合,提升任务并发控制。
结语
C++20 的协程为我们提供了一种更自然、更高效的异步编程方式。只需少量语法改动即可显著提升代码可读性和性能。希望通过本文,你能快速掌握协程的核心概念与实践技巧,并在实际项目中发挥出它的巨大价值。祝编码愉快!