在 C++20 标准中,协程(coroutines)被正式纳入标准库,提供了一套统一、轻量级的异步编程模型。与传统的多线程编程相比,协程在性能、可读性和资源管理方面都有显著优势。本文将从协程的基本概念、实现机制、与标准库的协作,以及实战案例等几个角度,对 C++ 协程进行系统梳理。
1. 协程的基本概念
协程是一种在单线程环境下可以挂起和恢复执行的函数。与线程不同,协程的挂起点是由程序员显式控制,协程可以在需要的时候主动让出控制权,随后在需要时恢复执行。协程的核心特点包括:
- 非抢占式切换:协程切换是通过
co_yield、co_await或co_return等关键字实现的,控制权的转移完全由协程内部决定。 - 轻量级:协程的栈空间与线程相比要小得多,甚至可以在堆中动态分配。
- 无共享状态:协程内部的局部变量默认保持独立,避免了多线程并发访问导致的数据竞争。
2. 协程的实现机制
在 C++20 里,协程由三大组件协同完成:
-
协程句柄(coroutine handle)
- 通过
std::coroutine_handle<>类型获取,表示对协程的控制权。 - 句柄提供
resume()、destroy()、done()等成员函数。
- 通过
-
悬停点(suspend point)
- 通过
co_await、co_yield、co_return等关键字产生。 - 每个悬停点对应一个
awaitable对象,该对象需要实现await_ready()、await_suspend()、await_resume()三个方法。
- 通过
-
协程 Promise
- 协程函数的返回类型为 `std::future ` 或 `std::generator` 时,编译器会生成对应的 Promise 对象。
- Promise 用来收集协程执行过程中的信息,如返回值、异常、等待对象等。
协程的编译器后端通过将协程函数拆分为若干个状态机步骤,将 co_await 等关键字插入状态机中,从而实现挂起与恢复。
3. 协程与 std::future、std::promise 的区别
std::future/std::promise依赖线程池或后台线程来完成异步操作,存在线程上下文切换成本。- 协程是同步语义的异步实现,调用者可以像写同步代码一样书写异步流程,编译器负责隐藏状态机细节。
4. C++ 协程的标准库支持
4.1 std::generator
`std::generator
` 是对协程的一种封装,用于产生一系列值。典型的使用场景包括: “`cpp std::generator range(int start, int end) { for (int i = start; i async_task() { return std::async(std::launch::async, []{ return 42; }); } std::future get_value() { std::future f = async_task(); int val = co_await std::experimental::make_ready_future(f.get()); // 等待 co_return val; } “` ### 4.3 std::experimental::awaitable 实验性标准库提供了一套 `awaitable` 接口,用于自定义协程的等待对象。通过实现 `await_ready`、`await_suspend`、`await_resume` 三个方法,用户可以让任何对象变成可 `co_await` 的。 ## 5. 协程实战:异步网络请求 下面给出一个简化的异步 HTTP 客户端示例,使用 Boost.Asio 的协程接口(`boost::asio::awaitable`)实现: “`cpp #include #include #include #include #include using boost::asio::ip::tcp; using namespace boost::asio::ip; // 简化的异步 GET 请求 boost::asio::awaitable async_get(tcp::resolver& resolver, const std::string& host, const std::string& target) { auto executor = co_await boost::asio::this_coro::executor; // 解析地址 auto endpoints = co_await resolver.async_resolve(host, “http”, boost::asio::use_awaitable); // 建立连接 tcp::socket socket(executor); co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable); // 发送请求 std::string request = “GET ” + target + ” HTTP/1.1\r\n” “Host: ” + host + “\r\n” “Connection: close\r\n\r\n”; co_await boost::asio::async_write(socket, boost::asio::buffer(request), boost::asio::use_awaitable); // 接收响应 boost::asio::streambuf response; co_await boost::asio::async_read_until(socket, response, “\r\n”, boost::asio::use_awaitable); std::istream resp_stream(&response); std::string http_version; unsigned int status_code; std::string status_message; resp_stream >> http_version >> status_code >> status_message; std::cout