**C++20 并发协程(std::coroutine)入门**

协程(coroutine)是 C++20 的新特性,提供了一种更简洁、更高效的异步编程模型。与传统的回调或线程相比,协程可以在单线程内实现多任务切换,避免了大量上下文切换开销。下面从概念、核心类、使用方式以及常见问题几个方面,帮助你快速掌握协程的基本用法。


1. 协程的核心概念

术语 说明
协程函数 带有 co_awaitco_yieldco_return 的函数
挂起点 co_await, co_yield, co_return 的位置,协程会在此暂停
协程句柄 `std::coroutine_handle
`,用于控制协程的生命周期
协程承诺 promise_type,协程内部的状态管理对象
状态机 编译器把协程函数自动转化为状态机

2. 基本协程例子

#include <coroutine>
#include <iostream>
#include <string_view>

struct Generator {
    struct promise_type {
        std::string_view current_value;
        std::string_view await_transform(std::string_view value) { return value; }
        Generator get_return_object() { return Generator{ std::coroutine_handle <promise_type>::from_promise(*this) }; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> coro;
    explicit Generator(std::coroutine_handle <promise_type> h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }

    bool next() {
        coro.resume();
        return !coro.done();
    }

    std::string_view value() const { return coro.promise().current_value; }
};

Generator countdown(int start) {
    for (int i = start; i >= 0; --i) {
        co_yield std::to_string(i);
    }
}

int main() {
    auto gen = countdown(5);
    while (gen.next()) {
        std::cout << "Value: " << gen.value() << '\n';
    }
}

说明:

  • co_yield 会把当前值保存到承诺对象中,然后挂起协程。
  • coro.resume() 重新激活协程,执行到下一个挂起点。
  • std::coroutine_handle 用于控制协程的开始、暂停、销毁。

3. co_awaitawaitable

co_await 主要用来等待异步操作完成。要使一个类型可被 co_await,需要满足以下接口:

struct awaitable {
    bool await_ready();   // 立即完成则返回 true
    void await_suspend(std::coroutine_handle<> h); // 需要挂起
    void await_resume();  // 恢复后返回的值
};

示例:等待一个异步 I/O 结果。

#include <future>
#include <chrono>

struct AsyncWait {
    std::future <void> fut;
    AsyncWait() : fut(std::async(std::launch::async, []{
        std::this_thread::sleep_for(std::chrono::seconds(1));
    })) {}
    bool await_ready() { return fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
    void await_suspend(std::coroutine_handle<> h) { std::thread([=]{
        fut.get();
        h.resume();
    }).detach(); }
    void await_resume() {}
};

Generator example() {
    co_await AsyncWait{};
    co_yield "After 1 second";
}

4. 协程的内存与生命周期

  • 堆 vs 栈:编译器会把协程函数的局部变量在协程对象中放到堆(由 promise_type 管理),保证挂起后状态保留。
  • 析构:如果协程没有 final_suspend 结束,就会导致资源泄露。通常使用 std::suspend_alwaysstd::suspend_never
  • 异常:在协程内部抛出异常会被 promise_type::unhandled_exception() 捕获,默认行为是调用 std::terminate()。可自定义处理。

5. 常见问题与解决

问题 解释 解决方案
协程句柄失效 调用 resume() 后没有保持句柄 在外部保持 std::coroutine_handle,不要在内部销毁
内存泄漏 未调用 destroy() 在类析构函数中销毁,或者使用 std::optional 自动管理
无返回值 co_return 需要匹配返回类型 确认 promise_typeget_return_object() 返回正确类型
多线程安全 协程本身不是线程安全的 在多线程访问前使用互斥锁或设计为线程本地
调试困难 协程状态机难以直接观察 通过 -g 生成调试信息,或使用第三方工具 cppinsights

6. 进一步学习资源

  • C++ 官方文档std::coroutinestd::experimental::generator 说明。
  • 书籍:《C++20 Cookbook》有协程章节。
  • 博客:多家 C++ 专栏已发布从入门到高级的协程教程。
  • 工具:Clang 的 -fsanitize=thread-fsanitize=address 对协程测试非常友好。

7. 结语

协程让异步编程变得更像同步代码,极大简化了逻辑。掌握了 co_awaitco_yieldco_returnpromise_type 的工作机制后,你可以轻松构建高性能网络、游戏或 GUI 相关的异步系统。希望本文能成为你迈入 C++20 并发协程世界的第一步。祝编码愉快!

发表评论