C++17中的协程:从概念到实践

C++17 中并未正式引入协程(coroutines),但它为协程的实现奠定了重要基础。协程是一种轻量级的子程序,能够在执行过程中挂起并在需要时恢复,适合处理异步IO、流式数据处理和状态机等场景。本文从概念入手,阐述协程在 C++ 生态中的作用,并给出一个基于 C++20 标准的协程实现示例,帮助读者快速上手。

1. 协程的核心概念

  1. 挂起与恢复
    协程在运行过程中可以被挂起(co_awaitco_yieldco_return),挂起点会保存协程的执行状态(如局部变量、指令指针)。随后,当协程再次被调用时,执行从挂起点继续。

  2. 协程句柄(std::coroutine_handle
    协程句柄是对协程的引用,负责控制协程的生命周期、挂起与恢复。

  3. 协程类型

    • generator:可产生一系列值的协程,类似于 Python 的生成器。
    • task:异步任务,最终返回一个结果或抛异常。
  4. awaitable
    一个可等待的对象,它实现了 await_ready(), await_suspend(), await_resume() 三个成员函数。

2. C++17 的前瞻性支持

虽然 C++17 没有正式的协程语法,以下特性为后续实现做准备:

  • std::experimental::coroutine_traits:定义协程返回类型。
  • std::experimental::coroutine_handle:协程句柄的实验性实现。
  • co_awaitco_yieldco_return 的语法已在实验阶段。

这些实验性特性在 C++20 标准中被正式引入,语法和库实现更完善。

3. C++20 协程的完整实现示例

下面给出一个简单的异步任务实现,用于模拟网络请求的非阻塞读取。

#include <iostream>
#include <coroutine>
#include <string>
#include <chrono>
#include <thread>

// 1. awaitable:模拟异步延迟
struct Delay {
    std::chrono::milliseconds ms;
    bool await_ready() const noexcept { return ms.count() == 0; }
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        std::thread([h, ms=ms]{
            std::this_thread::sleep_for(ms);
            h.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

// 2. task:异步任务类型
template<typename T>
struct Task {
    struct promise_type {
        T value_;
        Task get_return_object() {
            return Task{ std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_value(T value) { value_ = std::move(value); }
    };

    std::coroutine_handle <promise_type> handle_;
    Task(std::coroutine_handle <promise_type> h) : handle_(h) {}
    ~Task() { if (handle_) handle_.destroy(); }
    T get() { return handle_.promise().value_; }
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const noexcept { handle_.resume(); }
    T await_resume() const noexcept { return handle_.promise().value_; }
};

// 3. 异步函数
Task<std::string> fetch_from_network() {
    std::cout << "开始请求...\n";
    co_await Delay{std::chrono::milliseconds(2000)}; // 模拟延迟
    std::cout << "网络返回完成。\n";
    co_return "Hello, Coroutine!";
}

int main() {
    auto task = fetch_from_network();
    std::cout << "等待结果...\n";
    std::cout << "结果: " << task.get() << '\n';
    return 0;
}

说明

  • Delay 是一个 awaitable,用来模拟异步延迟。
  • Task 是一个简单的异步任务包装,支持 co_return 返回值。
  • fetch_from_network 使用 co_await 挂起并在延迟完成后恢复执行。

4. 协程的应用场景

场景 典型用途
异步IO 网络通信、文件读取、数据库查询
流式数据 数据流处理、事件驱动系统
状态机 游戏 AI、协议解析、渲染管线
并发控制 协程调度器、轻量级线程替代

5. 性能与注意事项

  • 堆栈消耗:协程在挂起时会保存局部变量,若过度使用可能导致堆栈膨胀。
  • 错误处理:协程异常必须通过 promise_type::unhandled_exception()co_await 的异常处理来捕获。
  • 调度策略:默认协程是协作式,需要自行调度(如 co_awaitawait_suspend 实现)。若需抢占式调度,可结合线程池或事件循环。

6. 结语

C++ 通过标准化协程,提供了更简洁、更高效的异步编程模型。虽然 C++17 已经为协程打下了实验性基础,但真正的生态落地还是在 C++20 之后。掌握协程的基本概念与实现方式后,你可以在项目中轻松构建高性能、可维护的异步代码。祝你编程愉快!

发表评论