C++20中如何借助coroutines与co_yield实现异步生成器?

来源:站长查询作者:日本程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《C++20中如何借助coroutines与co_yield实现异步生成器?》,敬请观看详情。同步遍历海量数据时主线程常被阻塞,异步生成器能把产出与消费解耦。C++20引入的协程通过co_yield暂停并让出值,配合自定义promise类型可构造懒求值序列。相比回调或手动状态机,协程代码更贴近普通循环写法,且能在等待IO时让出执行权。下文从promise设计、awaitable封装到网络轮询示例,说明如何用co_yield搭建可暂停的异步数据流,并分析其内存与调度开销。

在C++20中,协程(coroutines)为异步编程带来了原生语言支持,而co_yield关键字让我们能够轻松实现生成器模式。生成器是一种按需产出值的迭代结构,异步生成器则进一步允许在产出间隙让出线程去处理其他任务。本文将围绕promise类型、协程帧以及co_yield的工作机制,展示如何手写一个可用的异步生成器。

C++20中如何借助coroutines与co_yield实现异步生成器?

一、协程与co_yield的基本原理

C++20协程并非通过新线程实现并发,而是借助编译器生成的协程帧,将函数执行状态挂起与恢复。当一个函数包含co_yield、co_await或co_return时,它就被识别为协程。co_yield expr本质上会被编译器改写为co_await promise.yield_value(expr),这意味着我们可以通过定制promise类型来控制值的传递方式。

在生成器场景里,promise需要保存最近一次产出的值,并提供一种机制让外部迭代器能够逐步取走这些值。由于协程在co_yield处挂起,调用方不需要一次性跑完整个函数,而是每请求一次才推进到下一个yield点。这种懒求值特性在处理无限序列或大量IO数据时非常有用。

二、定义异步生成器的promise与返回类型

要实现一个生成器,我们需要定义返回类型(如AsyncGenerator),其内部嵌套一个promise_type。promise负责实现get_return_object、initial_suspend、final_suspend、yield_value和return_void等接口。下面的代码展示了一个最简化的整数异步生成器框架。

#include <coroutine>
#include <optional>

struct AsyncGenerator {
    struct promise_type {
        int current_value;
        std::coroutine_handle<promise_type> continuation;

        AsyncGenerator get_return_object() {
            return AsyncGenerator{ 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(int value) {
            current_value = value;
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;

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

    bool next() {
        if (!handle || handle.done()) return false;
        handle.resume();
        return !handle.done();
    }
    int value() const { return handle.promise().current_value; }
};

上面代码中,initial_suspend返回suspend_always,表示协程创建后先挂起,等待第一次next调用才启动。yield_value将传入的整数保存到current_value,并再次挂起。外部通过next恢复协程,用value读取产出值。

这种结构虽然简单,但已经具备生成器核心能力。如果希望它“异步”,只需在yield_value或恢复点插入co_await某个可等待对象,比如模拟IO就绪的awaitable,就能在等待期间释放线程。

三、让生成器真正异步:封装可等待对象

纯同步的生成器在yield之间不会让出线程,若要异步,需要协程在产出前co_await一个代表异步事件的awaitable。下面我们定义一个简单的定时让出awaitable,用来模拟非阻塞延迟。

#include <chrono>
#include <thread>

struct AsyncYield {
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const {
        std::thread([h]() mutable {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            h.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

AsyncYield在await_suspend中启动一个临时线程模拟异步回调,并在10毫秒后恢复协程。虽然真实项目应使用事件循环而非裸线程,但这足以说明co_await如何与co_yield配合。接下来我们改写生成器函数,让它在每次产出前异步等待。

AsyncGenerator count_async(int max) {
    for (int i = 0; i < max; ++i) {
        co_await AsyncYield{};
        co_yield i;
    }
}

在count_async中,每次循环先co_await AsyncYield{}让出执行权,再co_yield i将数字交给调用方。调用方的主逻辑不会被长时间占用,因为等待发生在独立线程中。这样便实现了“异步生成器”的基本形态。

四、使用场景与完整调用示例

假设我们需要从网络流式拉取数据并逐条处理,使用异步生成器可以避免把全部响应读入内存。下面示例在主函数中驱动生成器,并输出收到的数值。

#include <iostream>

int main() {
    auto gen = count_async(5);
    while (gen.next()) {
        std::cout << "got: " << gen.value() << std::endl;
    }
    return 0;
}

运行后程序会依次打印0到4,每次获取之间间隔约10毫秒。由于协程帧保存了局部变量i和挂起位置,循环状态在多次next调用间自然延续,代码可读性远高于手写状态机。

需要注意的是,当前实现未处理异常传播和协程取消。在生产环境中,应在promise的unhandled_exception中捕获并存储异常,由next调用方决定抛出;同时提供cancel方法强制destroy协程帧,防止资源泄漏。

五、优缺点与适用边界

使用C++20协程实现异步生成器的优势在于语法直观,循环和yield写法与同步代码几乎一致,降低了异步流程的理解成本。编译器自动管理协程帧,省去手动维护状态枚举的麻烦。此外,co_await可无缝接入现有异步IO库,如基于epoll或io_uring的awaitable。

缺点是协程帧分配在堆上,频繁创建大量生成器会带来内存与分配器压力;同时目前标准库未提供官方generator,所有promise逻辑需自行实现,容易在initial_suspend或final_suspend语义上出错。对于极致性能且生命周期极短的场景,传统回调可能更合适。总体来看,在流式处理、懒加载配置或测试数据构造中,co_yield异步生成器是简洁且高效的方案。

C++20coroutinesco_yield修改时间:2026-08-07 10:57:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。