**C++20协程:使用 co_yield 与 co_return 实现异步数据流**

在 C++20 之前,C++ 并没有原生的协程(coroutine)支持,所有异步逻辑都依赖回调、状态机或第三方库(如 Boost.Asio、cppcoro 等)。协程的引入让编写异步代码像写同步代码一样直观。本文以 co_yieldco_return 两个关键字为核心,演示如何利用协程实现一个可被异步消费的整数序列生成器,并讨论常见的使用陷阱。


1. 基础概念

  • 协程:在执行过程中可以被挂起(yield)或恢复的函数。协程保存其局部状态,允许在不同点继续执行。
  • co_yield:类似 yield,用于向调用者返回一个值并挂起协程。
  • co_return:协程结束时返回最终值,通常是一个 void 或聚合结果。
  • std::generator(实验性):C++20 标准库提供的协程生成器类型,封装了协程状态和迭代器逻辑。

由于 std::generator 仍处于实验阶段,在不同编译器(MSVC、Clang、GCC)中的支持略有差异,本文同时给出自定义实现的版本,便于在任何环境下复现。


2. 示例:异步整数序列生成器

2.1 使用 std::generator(GCC 11+ / Clang 13+)

#include <generator>
#include <iostream>

std::generator <int> async_range(int start, int end, int step = 1) {
    for (int i = start; i < end; i += step) {
        co_yield i;               // 返回当前值并挂起
    }
    co_return;                    // 结束协程
}

int main() {
    for (int n : async_range(0, 10, 2)) {
        std::cout << n << ' ';     // 输出: 0 2 4 6 8
    }
    std::cout << '\n';
}

说明

  • async_range 通过 co_yield 逐个生成数值,调用者可以像普通循环一样遍历。
  • 协程内部的所有局部变量(如 i)在挂起后会被保留,恢复时从上次 co_yield 的位置继续执行。

2.2 自定义实现(更具可移植性)

#include <coroutine>
#include <exception>
#include <iostream>

template<typename T>
class generator {
public:
    struct promise_type {
        T current_value;
        std::exception_ptr exception;

        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 {}; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }

        void unhandled_exception() { exception = std::current_exception(); }
        void return_void() {}
    };

    using handle_type = std::coroutine_handle <promise_type>;

    generator(handle_type h) : coro(h) {}
    generator(const generator&) = delete;
    generator(generator&& other) noexcept : coro(other.coro) {
        other.coro = nullptr;
    }
    ~generator() { if (coro) coro.destroy(); }

    struct iterator {
        handle_type coro;
        bool done = false;

        iterator(handle_type h, bool d) : coro(h), done(d) {
            if (coro && !done) {
                coro.resume();
                if (coro.done()) done = true;
            }
        }

        iterator& operator++() {
            if (!done) {
                coro.resume();
                if (coro.done()) done = true;
            }
            return *this;
        }

        T operator*() const { return coro.promise().current_value; }

        bool operator==(std::default_sentinel_t) const { return done; }
        bool operator!=(std::default_sentinel_t) const { return !done; }
    };

    iterator begin() {
        return iterator{coro, false};
    }
    std::default_sentinel_t end() { return {}; }

private:
    handle_type coro;
};

generator <int> async_range(int start, int end, int step = 1) {
    for (int i = start; i < end; i += step) {
        co_yield i;
    }
    co_return;
}

int main() {
    for (int n : async_range(1, 5)) {
        std::cout << n << ' ';   // 输出: 1 2 3 4
    }
}

关键点

  • promise_type 用于维护协程状态;yield_value 保存当前值,initial_suspend / final_suspend 控制挂起行为。
  • iterator 把协程视为可迭代对象,隐藏挂起/恢复细节。
  • 该实现兼容所有支持 C++20 协程的编译器。

3. 常见陷阱与最佳实践

陷阱 解释 解决方案
协程对象泄漏 如果未正确 destroy(),会导致堆栈泄漏。 在 RAII 中管理协程句柄,使用类包装(如上例)自动销毁。
异常传播 co_yield 后若抛出异常,协程会进入异常状态。 promise_type 中实现 unhandled_exception,并在迭代器中捕获 exception_ptr
多线程使用 协程本身不是线程安全的,若多线程访问同一协程,需要同步。 每个线程使用独立的协程实例,或者使用互斥锁包装迭代器。
内存占用 每个挂起点保留局部变量,若局部变量巨大,可能导致堆栈膨胀。 避免在协程中保存大型对象,改用指针或引用,或使用 std::pmr
编译器支持差异 GCC 的 std::generator 在 11 版后才支持;MSVC 目前仍处于实验。 使用自定义实现或使用第三方库(cppcoro、cppcoro-impl)。

4. 与传统异步模式比较

方式 复杂度 可读性 性能
回调链 低(频繁的堆分配)
Promise/Future
async/await(协程) 高(无额外分配)

协程将状态机的拆解、上下文切换等细节封装为编译器层面,消除了手动管理的负担。通过 co_yield,我们可以像写同步代码一样编写异步迭代器,极大提升开发效率。


5. 结语

C++20 协程是一次重要的语言演进,它把异步编程带回了 C++ 的核心。通过 co_yieldco_return,我们可以构建简洁、可维护且性能优秀的异步数据流。无论你是想处理文件 I/O、网络请求还是并行计算,协程都能提供一种更自然、更安全的实现方式。希望本文的示例与实战建议能帮助你在项目中快速上手协程,享受更高层次的抽象与更优雅的代码。

发表评论