C++20 引入了协程(coroutines)这一强大的语言特性,为异步编程、生成器以及延迟计算提供了天然的语法支持。本文将从协程的基本概念、实现机制、关键标准库组件以及一个完整的异步任务示例,系统地梳理 C++20 协程的核心知识点。
一、协程的基本概念
协程是一种可以挂起和恢复执行的函数,其执行状态会被保存,允许在后续继续执行。与传统的线程相比,协程是轻量级的,可在单线程内实现多任务并发;与回调函数相比,协程可以写出更接近同步的代码结构,提升可读性和可维护性。
C++20 对协程的支持分为三个层面:
- 协程语法:
co_await、co_yield、co_return。 - 协程句柄(promise):定义协程的生命周期、状态与返回值。
- 协程包装器:标准库提供的
std::experimental::coroutine_handle、std::experimental::generator、std::future等工具。
二、协程实现原理
当编译器遇到 co_await、co_yield 或 co_return 时,会把函数拆分为若干“块”,并在每个挂起点插入代码,构成一个状态机。
- promise_type:协程体外部的类,用于保存协程状态、结果、异常等。
- coroutine_handle:指向 promise 对象的句柄,提供
resume()、destroy()等操作。 - 悬挂/恢复:
co_await expr在表达式expr产生挂起点时,协程会返回给调用者,后续通过handle.resume()继续执行。
三、标准库中的协程工具
| 组件 | 作用 | 示例 |
|---|---|---|
| `std::experimental::generator | ||
| 简单的生成器,支持co_yield|generator seq(){ for(int i=0;i<10;++i) co_yield i; }` |
||
| `std::future | ||
| 未来值,兼容co_await|future foo(){ co_return 42; }` |
||
std::async + co_await |
结合异步任务 | auto fut = std::async(std::launch::async, []{ return 5; }); int v = co_await fut; |
| `std::experimental::task | ||
| 轻量级异步任务 |task async_add(int a, int b){ co_return a+b; }` |
由于标准化进程的原因,部分协程相关类型仍在
std::experimental命名空间中;在 C++23 起将正式迁移到std。
四、实战示例:异步 HTTP 请求
下面演示如何使用 C++20 协程结合 ASIO 进行异步 HTTP GET。示例仅为演示协程使用,省略了完整的错误处理和 TLS 支持。
#include <iostream>
#include <asio.hpp>
#include <experimental/coroutine>
#include <experimental/task>
using asio::ip::tcp;
namespace coro = std::experimental;
coro::task<std::string> async_http_get(const std::string& host, const std::string& path)
{
asio::io_context io_context;
// Resolve
tcp::resolver resolver(io_context);
auto endpoints = co_await resolver.async_resolve(host, "80", asio::use_coro);
// Connect
tcp::socket socket(io_context);
co_await asio::async_connect(socket, endpoints, asio::use_coro);
// Send request
std::string request = "GET " + path + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Connection: close\r\n\r\n";
co_await asio::async_write(socket, asio::buffer(request), asio::use_coro);
// Read response
asio::streambuf buffer;
std::string response;
while (true)
{
std::size_t n = co_await asio::async_read(socket, buffer, asio::transfer_at_least(1), asio::use_coro);
std::istream is(&buffer);
std::string chunk(n, '\0');
is.read(&chunk[0], n);
response += chunk;
if (n < 1) break;
}
co_return response;
}
int main()
{
try
{
auto fut = async_http_get("www.example.com", "/");
std::string body = fut.get(); // blocking get, for demo only
std::cout << body << std::endl;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
}
说明
asio::use_coro是 ASIO 的协程适配器,让其返回可co_await的对象。async_http_get在整个过程中使用co_await,代码结构清晰、顺序化。- 最终
fut.get()在主线程等待异步结果;在实际项目中可使用co_await进一步链式调用。
五、协程与性能
协程的优势主要体现在:
- 减少线程上下文切换:协程是用户态的,切换成本极低。
- 更易维护:同步化的代码逻辑比回调链条更易读。
- 资源占用更小:协程的栈通常在 4KB 左右,远低于线程堆栈。
但需要注意:
- 过度使用协程可能导致大量状态机对象堆栈,影响缓存局部性。
- 异步 I/O 库对协程的支持不均衡,需选择成熟的适配器。
六、学习与实践建议
- 先掌握同步编程:理解
std::thread、std::future等基础。 - 阅读标准规范:重点查看协程相关章节,理解
promise_type的生命周期。 - 实践小项目:如实现一个协程版的任务调度器或简单的网络服务器。
- 关注社区实现:如 cppcoro、asio 的协程适配器,了解实际实现细节。
结语
C++20 的协程为现代 C++ 开发注入了新的活力,既能简化异步逻辑,又能保持高性能。掌握协程的核心原理、标准库工具和实际应用案例,将使你在面对复杂异步需求时游刃有余。继续深入学习,探索协程与模板元编程、并行算法的深度结合,便能在 C++ 生态中打造更强大、更高效的程序。