随着多核处理器的普及,程序员越来越需要高效的并发模型。C++20首次引入协程(coroutine)作为语言特性,为异步编程提供了更直观的语法。然而,在实际项目中,协程的使用仍存在诸多挑战。本篇文章将回顾C++20协程的基本原理,探讨C++23对协程的改进,并给出一套实战范例,帮助你在项目中更好地应用协程。
一、C++20协程概述
-
关键语法
co_await:等待一个 awaitable 对象完成。co_yield:从协程产生一个值给调用者。co_return:终止协程并返回值。
-
协程类型
- `generator `:可产出一系列值。
- `task `:异步任务,返回值类型为 `T`。
generator需要配合promise_type实现细节。
-
调度与执行
协程本身不决定何时恢复;它们的恢复由用户提供的调度器决定。C++标准库未提供调度器,需要自行实现或使用第三方库。
二、C++23对协程的增强
-
std::suspend_always 与 std::suspend_never 改进
` 头文件中,避免了需要手动实现 `await_transform` 的情况。
C++23 将这两个类移动到 ` -
std::generator 的改进
- 支持
begin()/end()接口,直接与范围-based for 循环兼容。 std::generator::promise_type提供get_return_object_on_allocation_failure。
- 支持
-
标准化的协程调度器
C++23 引入std::experimental::coroutine_traits的扩展,以便在库内部更好地管理协程。
这意味着在未来的标准库中会出现统一的调度器接口,例如std::task可以被默认的std::async结合使用。 -
错误处理
C++23 为协程提供了更好的异常传播机制。co_await可以抛出异常,协程的promise_type::final_suspend现在可以返回std::suspend_always来保证异常被正确捕获。
三、实战案例:异步网络请求
下面展示一个简单的异步 HTTP GET 请求实现,使用 C++20 协程,并结合 C++23 的改进。我们使用 Boost.Asio 做底层 I/O。
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <coroutine>
#include <iostream>
namespace asio = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
// 简单的协程包装器,使用 asio::awaitable
using awaitable = asio::awaitable<std::string>;
// 一个异步 GET 请求协程
awaitable async_get(const std::string& host, const std::string& target)
{
asio::ip::tcp::resolver resolver{co_await asio::this_coro::executor};
auto const results = co_await resolver.async_resolve(host, "http", asio::use_awaitable);
beast::tcp_stream stream{co_await asio::this_coro::executor};
co_await stream.async_connect(results, asio::use_awaitable);
// 构造 HTTP 请求
http::request<http::empty_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// 发送请求
co_await http::async_write(stream, req, asio::use_awaitable);
// 接收响应
beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
co_await http::async_read(stream, buffer, res, asio::use_awaitable);
// 关闭连接
co_await stream.socket().shutdown(asio::ip::tcp::socket::shutdown_both, std::ignore);
std::stringstream ss;
ss << beast::buffers_to_string(res.body().data());
co_return ss.str();
}
int main()
{
asio::io_context ioc{1};
auto fut = async_get("www.example.com", "/");
fut = fut
.then([](awaitable::reenter_type & reenter, awaitable::result_type&& result)
{
std::cout << "Response:\n" << result << std::endl;
reenter(); // 结束协程
});
ioc.run();
}
关键点说明
asio::this_coro::executor提供了协程所在的 I/O 上下文。asio::use_awaitable将异步操作转换为协程-friendly。co_return直接返回结果,调用者可以像std::future一样使用then。
四、协程调度建议
-
使用线程池
为了避免阻塞主线程,建议将协程的调度交给一个线程池。Boost.Asio 的io_context::run本身就是一个多线程调度器。 -
避免过度嵌套
过多的co_await嵌套会导致堆栈增长,最好将协程拆分成更小的子任务。 -
异常安全
所有异步操作都可能抛出boost::system::system_error,务必在协程内部捕获或让异常向上传递。
五、总结
- C++20 为协程奠定了基础,但需要自行完成调度和错误处理。
- C++23 在标准化协程类型、提供更友好的 API 以及改进错误处理方面做出了重要贡献。
- 在实际项目中,配合 Boost.Asio 或类似的 I/O 库,可以快速构建高性能、易维护的异步应用。
随着未来标准的进一步完善,协程将成为 C++ 并发编程的核心工具,值得每个 C++ 开发者深入学习和掌握。