协程(Coroutines)是 C++20 引入的一项强大特性,它为编写异步代码提供了简洁、可读性高的方式。相较于传统的回调或 Future 机制,协程让代码在逻辑上保持顺序,极大地降低了错误率。本文将从协程的基本概念、实现原理、使用示例以及性能提升等方面进行系统阐述,帮助你快速掌握并在项目中应用协程。
一、协程概念回顾
协程是一种轻量级线程,允许在函数内部暂停(yield)并在之后恢复(resume)。与线程不同,协程在同一线程中执行,切换开销极低。C++ 的协程使用 co_await、co_yield、co_return 等关键字,配合 std::coroutine_handle、std::suspend_always、std::suspend_never 等辅助类型实现。
co_await:在协程内部等待另一个协程或未来值完成。co_yield:产生一个值,暂停执行,等待下次恢复。co_return:返回最终结果并结束协程。
二、协程的执行模型
协程的生命周期由 promise 与 handle 两部分组成:
class MyPromise {
public:
MyReturnType get_return_object() { ... }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(MyReturnType value) { ... }
void unhandled_exception() { ... }
};
- Promise 存储协程执行所需的数据。
- Handle 用于控制协程的挂起/恢复。
编译器在编译时会把协程拆解为若干状态机函数,执行时通过 handle.resume() 控制状态流。
三、典型使用场景
- 异步 I/O:与网络库(如 Boost.Asio)配合,使用
co_await等待 socket 读写完成。 - 事件驱动:在事件循环中,协程可以作为事件回调,实现顺序式的事件处理。
- 任务并行:利用协程和多线程池,轻松实现任务的并行执行与结果聚合。
四、案例:异步 HTTP 客户端
下面给出一个使用 C++20 协程实现的简易异步 HTTP GET 客户端,基于 boost::asio 的异步功能。
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#include <string>
using boost::asio::ip::tcp;
using boost::asio::awaitable;
using namespace std::chrono_literals;
awaitable <void> async_http_get(const std::string& host, const std::string& path)
{
auto executor = co_await boost::asio::this_coro::executor;
tcp::resolver resolver(executor);
tcp::socket socket(executor);
// Resolve host
auto endpoints = co_await resolver.async_resolve(host, "http", boost::asio::use_awaitable);
// Connect
co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
// Build request
std::string request = "GET " + path + " HTTP/1.1\r\n";
request += "Host: " + host + "\r\n";
request += "Connection: close\r\n\r\n";
// Send request
co_await boost::asio::async_write(socket,
boost::asio::buffer(request),
boost::asio::use_awaitable);
// Receive response
boost::asio::streambuf buffer;
std::ostream out{&buffer};
boost::asio::async_read_until(socket, buffer, "\r\n", boost::asio::use_awaitable);
std::string status_line;
std::getline(out, status_line);
std::cout << "Status: " << status_line << '\n';
// Read headers
while (true) {
co_await boost::asio::async_read_until(socket, buffer, "\r\n\r\n", boost::asio::use_awaitable);
std::string header;
std::getline(out, header);
if (header == "\r") break;
std::cout << header << '\n';
}
// Read body
while (socket.available() > 0) {
co_await boost::asio::async_read(socket, buffer.prepare(1024), boost::asio::use_awaitable);
buffer.commit(1024);
std::cout << &buffer;
}
}
int main()
{
try {
boost::asio::io_context io_context{1};
boost::asio::co_spawn(io_context,
async_http_get("example.com", "/"),
boost::asio::detached);
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << '\n';
}
}
关键点说明:
co_await直接挂起协程,等待异步操作完成后恢复。boost::asio::use_awaitable指定返回awaitable类型。boost::asio::co_spawn用于将协程挂载到io_context。
五、性能优势
- 切换开销低:协程切换由编译器生成的状态机完成,堆栈切换被避免,性能远优于线程切换。
- 资源占用小:协程不需要单独的线程栈,内存占用可按需分配,适合高并发场景。
- 代码简洁:异步代码保持同步写法,易于阅读与维护,减少错误率。
六、常见坑与优化
| 典型问题 | 解决方案 |
|---|---|
| 协程堆栈溢出 | 通过 co_yield 分步执行,或使用 std::suspend_always 控制暂停点 |
| 资源泄漏 | 确保 promise 的 unhandled_exception() 能捕获异常,使用 RAII 包装资源 |
| 与旧库冲突 | 若使用第三方库不支持协程,需使用桥接函数或包装为 std::future |
七、结语
C++20 的协程为异步编程提供了更高层次的抽象,使得并发代码既易写又易读。通过合适的事件循环和协程库(如 Boost.Asio、cppcoro、libuv),你可以在性能与开发效率之间取得良好平衡。希望本文能帮助你快速上手协程,并在实际项目中充分发挥其优势。