协程(Coroutine)是实现异步编程的一种强大机制,它让我们能够在单线程中写出看似同步、但实际运行时是非阻塞的代码。C++ 通过标准化协程(C++20 起)与 Boost 等第三方库提供了完整的协程生态,使得异步编程变得更为直观和高效。本文将从协程的基本概念、实现方式、以及在现代 C++ 项目中的实际应用来展开讨论。
1. 协程的基本概念
协程是一种比线程更轻量级的计算单元。与线程不同,协程共享同一线程的栈空间,在执行时可以暂停(co_await、co_yield)并在需要时恢复。协程的暂停与恢复由编译器生成的状态机来管理,程序员只需要关注业务逻辑即可。
协程的核心语义可以归纳为:
- 挂起(suspend):协程在执行过程中遇到
co_await、co_yield或co_return时会挂起,返回给调用者。 - 恢复(resume):调用者或事件循环触发协程恢复执行,直至再次挂起或结束。
2. Boost.Coroutine 与 Boost.Asio
在 C++20 标准化之前,Boost.Coroutine 提供了两种协程实现:
- 协作式协程:使用
boost::coroutines::coroutine,适合单线程协程的场景。 - 协作式异步协程:结合
boost::asio的async_*函数,支持 I/O 异步操作。
Boost.Asio 通过 async_* 函数配合 io_context 实现了事件驱动的异步 I/O。典型的使用方式如下:
#include <boost/asio.hpp>
void async_read(boost::asio::ip::tcp::socket& socket, std::vector <char>& buffer) {
socket.async_read_some(boost::asio::buffer(buffer),
[](boost::system::error_code ec, std::size_t bytes_transferred){
if (!ec) {
// 处理数据
}
});
}
通过回调函数的形式,Boost.Asio 实现了协程式的异步编程模型。虽然回调层数较多,但 Boost.Asio 的性能与灵活性在实际项目中得到广泛验证。
3. C++20 标准协程
C++20 对协程的支持主要体现在以下几个关键特性:
co_await:用于挂起协程,等待一个 awaitable 对象完成。co_yield:产生一个值并挂起,适用于生成器模式。co_return:返回协程最终结果并结束协程。std::coroutine_handle:底层句柄,用于控制协程的生命周期。
标准协程需要实现一个 awaitable 类型,典型的实现需要包含:
struct awaitable {
bool await_ready() noexcept { /* ... */ }
void await_suspend(std::coroutine_handle<> h) noexcept { /* ... */ }
T await_resume() noexcept { /* ... */ }
};
使用标准协程实现一个简单的异步 I/O 例子:
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
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 unhandled_exception() noexcept {}
void return_void() noexcept {}
};
};
task async_sleep(std::chrono::milliseconds ms) {
std::this_thread::sleep_for(ms);
co_return;
}
int main() {
async_sleep(1000);
std::cout << "Finished sleeping\n";
}
虽然上例只是同步阻塞,但它演示了协程语法。真正的异步 I/O 需要将 std::this_thread::sleep_for 替换为非阻塞等待,例如与 asio 或自定义事件循环结合。
4. 生成器模式:co_yield 的魅力
co_yield 让协程可以像迭代器一样产出一系列值,极大简化了生成器的实现。例如,生成斐波那契数列:
#include <coroutine>
#include <iostream>
struct generator {
struct promise_type {
int current_value;
generator get_return_object() { return {}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) noexcept {
current_value = value;
return {};
}
void return_void() noexcept {}
void unhandled_exception() noexcept {}
};
struct iterator {
std::coroutine_handle <promise_type> coro;
int value;
iterator(std::coroutine_handle <promise_type> h) : coro(h) {
if (coro)
value = coro.promise().current_value;
}
iterator& operator++() {
coro.resume();
if (coro.done()) coro = nullptr;
else value = coro.promise().current_value;
return *this;
}
int operator*() const { return value; }
bool operator==(std::default_sentinel_t) const { return !coro; }
};
iterator begin() {
auto h = std::coroutine_handle <promise_type>::from_promise(*this);
h.resume();
return iterator(h);
}
std::default_sentinel_t end() { return {}; }
};
generator fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
int main() {
for (auto x : fibonacci(10))
std::cout << x << ' ';
std::cout << '\n';
}
运行结果为:0 1 1 2 3 5 8 13 21 34。co_yield 的实现让生成器的写法与 std::vector 的使用方式一脉相承,代码简洁且易于维护。
5. 与 std::future 与 std::promise 的区别
传统的 std::future / std::promise 也支持异步结果传递,但它们是基于线程/任务的同步机制,无法做到协程内部的挂起/恢复。协程通过 co_await 对 awaitable 对象进行挂起,整个过程不涉及额外线程,降低了上下文切换成本。
此外,std::future 的 get() 会阻塞,除非使用 wait_for 或 wait_until。而协程的 await_resume() 在挂起对象完成后直接返回值,保持了异步非阻塞的本质。
6. 实际项目中的协程使用技巧
-
与 IO 框架配合
在网络编程中,将协程与事件循环框架(如asio::io_context、libuv或自研 loop)结合,使用co_await等待异步事件完成。这样可以避免回调地狱,使代码保持同步式结构。 -
错误处理
协程内的异常可以通过try-catch捕获,并在await_resume()中重新抛出或返回错误码。std::exception_ptr可用于跨协程传播异常。 -
性能调优
- 只在真正需要异步 I/O 的地方使用协程。
- 通过
std::suspend_always/std::suspend_never控制挂起点,避免不必要的上下文切换。 - 在生成器中尽量使用
co_yield产生的值进行惰性计算,避免一次性生成大量数据导致内存占用。
-
协程池
对于需要大量短生命周期协程的场景,可实现协程池或协程任务调度器,以复用协程句柄和减少堆栈分配。
7. 未来趋势
C++ 标准库已经为协程奠定了基础,但真正的异步编程仍然依赖于成熟的 I/O 库与事件循环。随着 C++23 与后续标准的推出,协程相关的工具(如 std::generator、std::task、std::coroutine_traits)将进一步完善,语言层面也会提供更多便利的语法糖。
8. 结语
协程让 C++ 的异步编程从回调到同步式代码变得自然。借助 Boost 及 C++20 标准提供的协程机制,程序员可以在保持代码可读性的同时,充分利用系统资源,构建高性能、高可扩展性的应用。无论是网络服务器、游戏引擎还是大数据处理,协程都是不可或缺的技术武器。欢迎大家在项目中大胆尝试并分享经验,共同推动 C++ 异步编程的落地与成熟。