在 C++20 中,协程(coroutine)被正式引入语言层面,提供了一种自然、轻量的方式来实现异步流式计算。相较于传统的回调、线程或消息队列,协程可以让代码保持同步写法,却能够隐藏底层的异步等待细节。下面从基本概念、语法特性到完整示例,系统讲解如何在 C++20 中使用协程实现异步编程。
1. 基本概念
| 术语 | 说明 |
|---|---|
co_await |
暂停协程执行,等待一个可等待对象完成 |
co_yield |
生成一个值给调用者,并暂停协程 |
co_return |
结束协程并返回最终值 |
| `task | |
| 一种表示异步结果的类型,类似std::future`,但更轻量 |
协程的关键是 挂起(suspend)与 恢复(resume)。当协程遇到 co_await 时,它会将控制权交还给外部调度器,等待等待对象完成后再恢复执行。这样可以避免阻塞线程。
2. 语法要点
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
- promise_type:协程的“承诺”对象,负责管理协程生命周期。你可以在里面自定义
initial_suspend、final_suspend、return_value等。 - suspend_never:表示不挂起。通常
initial_suspend需要挂起,final_suspend可以根据需要决定是否挂起。
3. 示例:异步网络请求
下面的示例演示了一个基于 asio(Boost.Asio)的简单 HTTP GET 请求,并使用协程进行异步处理。为了简化,省略了错误处理。
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>
#include <string>
using namespace boost::asio;
using namespace boost::asio::ip;
namespace ssl = boost::asio::ssl;
struct async_task {
struct promise_type;
using handle_type = std::coroutine_handle <promise_type>;
handle_type coro;
async_task(handle_type h) : coro(h) {}
~async_task() { if (coro) coro.destroy(); }
struct promise_type {
async_task get_return_object() { return async_task{handle_type::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
async_task fetch_https(const std::string& host, const std::string& path) {
io_context ctx;
ssl::context ctx_ssl{ssl::context::sslv23_client};
tcp::resolver resolver(ctx);
auto endpoints = co_await resolver.async_resolve(host, "https", use_awaitable);
tcp::socket socket(ctx);
co_await async_connect(socket, endpoints, use_awaitable);
ssl::stream<tcp::socket> ssl_stream(std::move(socket), ctx_ssl);
co_await ssl_stream.async_handshake(ssl::stream_base::client, use_awaitable);
std::string request = "GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n";
co_await async_write(ssl_stream, buffer(request), use_awaitable);
boost::asio::streambuf response;
co_await async_read_until(ssl_stream, response, "\r\n\r\n", use_awaitable);
std::istream resp_stream(&response);
std::string status_line;
std::getline(resp_stream, status_line);
std::cout << "Response status: " << status_line << std::endl;
// 继续读取 body
co_await async_read(ssl_stream, response, transfer_all(), use_awaitable);
std::cout << "Body:\n" << &response << std::endl;
}
int main() {
io_context ctx;
fetch_https("www.example.com", "/").coro.promise().coro.resume(); // 手动启动协程
ctx.run();
}
说明
use_awaitable是 Boost.Asio 提供的用于协程的 awaitable 适配器。co_await与async_*组合实现了非阻塞 I/O。async_task封装了协程句柄,简化了协程的创建和销毁。
4. 协程与 std::future 的对比
| 特点 | std::future |
co_await / 协程 |
|---|---|---|
| 线程开销 | 可能涉及线程 | 无线程,轻量挂起 |
| 错误传播 | get() 会抛异常 |
通过 promise_type 统一处理 |
| 代码可读性 | 嵌套回调 | 直观同步写法 |
| 资源管理 | shared_future/promise |
co_return、co_yield 控制 |
协程在 I/O 密集型任务、事件驱动模型中表现更佳,尤其与 ASIO、libuv 等事件循环配合使用时。
5. 常见陷阱
- 忘记挂起:若
initial_suspend返回suspend_never,协程会立即执行完毕,可能导致后续co_await无法正常挂起。 - 生命周期管理:协程对象若超出作用域导致句柄失效,需要使用
std::shared_ptr或std::async等包装。 - 异常捕获:协程内部抛出的异常会传递给
promise_type::unhandled_exception,需自行决定是否抛出或记录。
6. 小结
C++20 协程为异步编程提供了语言级别的原语,让我们可以像写同步代码一样书写异步逻辑。结合现代 I/O 库(如 Boost.Asio、libuv 等),协程成为构建高性能网络服务器、客户端的核心技术之一。掌握 co_await、co_yield、promise_type 的细节,将帮助你在项目中写出更简洁、更易维护的异步代码。