C++20 协程:从概念到实践

C++20 引入了协程(coroutines)这一强大的语言特性,为异步编程、生成器以及延迟计算提供了天然的语法支持。本文将从协程的基本概念、实现机制、关键标准库组件以及一个完整的异步任务示例,系统地梳理 C++20 协程的核心知识点。

一、协程的基本概念

协程是一种可以挂起和恢复执行的函数,其执行状态会被保存,允许在后续继续执行。与传统的线程相比,协程是轻量级的,可在单线程内实现多任务并发;与回调函数相比,协程可以写出更接近同步的代码结构,提升可读性和可维护性。

C++20 对协程的支持分为三个层面:

  1. 协程语法co_awaitco_yieldco_return
  2. 协程句柄(promise):定义协程的生命周期、状态与返回值。
  3. 协程包装器:标准库提供的 std::experimental::coroutine_handlestd::experimental::generatorstd::future 等工具。

二、协程实现原理

当编译器遇到 co_awaitco_yieldco_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 进一步链式调用。

五、协程与性能

协程的优势主要体现在:

  1. 减少线程上下文切换:协程是用户态的,切换成本极低。
  2. 更易维护:同步化的代码逻辑比回调链条更易读。
  3. 资源占用更小:协程的栈通常在 4KB 左右,远低于线程堆栈。

但需要注意:

  • 过度使用协程可能导致大量状态机对象堆栈,影响缓存局部性。
  • 异步 I/O 库对协程的支持不均衡,需选择成熟的适配器。

六、学习与实践建议

  1. 先掌握同步编程:理解 std::threadstd::future 等基础。
  2. 阅读标准规范:重点查看协程相关章节,理解 promise_type 的生命周期。
  3. 实践小项目:如实现一个协程版的任务调度器或简单的网络服务器。
  4. 关注社区实现:如 cppcoro、asio 的协程适配器,了解实际实现细节。

结语

C++20 的协程为现代 C++ 开发注入了新的活力,既能简化异步逻辑,又能保持高性能。掌握协程的核心原理、标准库工具和实际应用案例,将使你在面对复杂异步需求时游刃有余。继续深入学习,探索协程与模板元编程、并行算法的深度结合,便能在 C++ 生态中打造更强大、更高效的程序。

发表评论