C++协程的未来:从C++20到C++23的进化

随着多核处理器的普及,程序员越来越需要高效的并发模型。C++20首次引入协程(coroutine)作为语言特性,为异步编程提供了更直观的语法。然而,在实际项目中,协程的使用仍存在诸多挑战。本篇文章将回顾C++20协程的基本原理,探讨C++23对协程的改进,并给出一套实战范例,帮助你在项目中更好地应用协程。

一、C++20协程概述

  1. 关键语法

    • co_await:等待一个 awaitable 对象完成。
    • co_yield:从协程产生一个值给调用者。
    • co_return:终止协程并返回值。
  2. 协程类型

    • `generator `:可产出一系列值。
    • `task `:异步任务,返回值类型为 `T`。
    • generator 需要配合 promise_type 实现细节。
  3. 调度与执行
    协程本身不决定何时恢复;它们的恢复由用户提供的调度器决定。C++标准库未提供调度器,需要自行实现或使用第三方库。

二、C++23对协程的增强

  1. std::suspend_always 与 std::suspend_never 改进
    C++23 将这两个类移动到 `

    ` 头文件中,避免了需要手动实现 `await_transform` 的情况。
  2. std::generator 的改进

    • 支持 begin()/end() 接口,直接与范围-based for 循环兼容。
    • std::generator::promise_type 提供 get_return_object_on_allocation_failure
  3. 标准化的协程调度器
    C++23 引入 std::experimental::coroutine_traits 的扩展,以便在库内部更好地管理协程。
    这意味着在未来的标准库中会出现统一的调度器接口,例如 std::task 可以被默认的 std::async 结合使用。

  4. 错误处理
    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

四、协程调度建议

  1. 使用线程池
    为了避免阻塞主线程,建议将协程的调度交给一个线程池。Boost.Asio 的 io_context::run 本身就是一个多线程调度器。

  2. 避免过度嵌套
    过多的 co_await 嵌套会导致堆栈增长,最好将协程拆分成更小的子任务。

  3. 异常安全
    所有异步操作都可能抛出 boost::system::system_error,务必在协程内部捕获或让异常向上传递。

五、总结

  • C++20 为协程奠定了基础,但需要自行完成调度和错误处理。
  • C++23 在标准化协程类型、提供更友好的 API 以及改进错误处理方面做出了重要贡献。
  • 在实际项目中,配合 Boost.Asio 或类似的 I/O 库,可以快速构建高性能、易维护的异步应用。

随着未来标准的进一步完善,协程将成为 C++ 并发编程的核心工具,值得每个 C++ 开发者深入学习和掌握。

发表评论