导读:本期聚焦于小伙伴创作的《如何用C++20协程实现简单的任务调度?co_await与Promise源码解析》,敬请观看详情。把多个异步操作放进一个调度器里顺序或并发执行,传统回调写法容易陷入嵌套地狱。C++20引入的协程通过co_await挂起和恢复执行流,让异步代码像同步一样直白。本文给出可编译的轻量任务调度示例,核心是一个满足Promise标准的task类型,它在co_await时把后续逻辑交给调度器排隊,待资源就绪再切回原帧。对比手动写状态机,协程把悬挂点、恢复点交给编译器生成,开发效率明显提升,但也要求理解promise_type里的initial_suspend与final_suspend语义,否则会出现协程帧提前销毁或永不 resumed 的 bug。

在C++20标准中,协程作为语言级特性正式落地,它允许函数在执行过程中挂起并在之后恢复,而不阻塞线程。借助协程,我们可以以同步的写法完成异步任务调度。本文将从底层原理出发,结合完整源码,讲解如何使用co_await与Promise实现一个简单的任务调度器。

如何用C++20协程实现简单的任务调度?co_await与Promise源码解析

一、C++20协程的核心概念

C++20协程并不是通过新增关键字定义函数体,而是当函数中出现co_awaitco_yieldco_return时,编译器会将其识别为协程。协程的执行依赖于一个关联的Promise对象,该对象由编译器通过函数的返回类型中的promise_type创建。Promise负责控制协程的初始挂起、最终挂起以及返回值传递。

当协程被调用时,首先创建协程帧,其中包含Promise、参数副本和局部变量。随后调用promise_type::initial_suspend决定是否在开头挂起。通常调度器会令其挂起,以便将协程交给事件循环。协程遇到co_await表达式时,会调用操作数的await_readyawait_suspendawait_resume,从而把控制权移出协程。

1.1 Promise与Awaiter的关系

Promise类型是协程与外部世界的桥梁。它必须定义get_return_object以返回给调用者的句柄,initial_suspendfinal_suspend返回awaiter控制挂起行为,return_voidreturn_value处理完成逻辑。Awaiter则是被co_await的对象,它决定协程如何暂停与恢复。

在我们的任务调度器中,Promise会在协程结束时将自身标记为完成,并通知调度器唤醒等待该任务的其它协程。这种机制避免了手动管理状态机,使异步代码保持线性结构。

二、任务调度器的设计思路

简单任务调度器的目标是:能够提交多个协程任务,在单线程内通过协作式调度依次推进,当任务因等待资源而挂起时,调度器去执行其它就绪任务。我们使用一个就绪队列存储可恢复的协程句柄,并在每个任务完成或挂起时让出控制权。

核心组件包括:task作为协程返回类型,其内部Promise记录所属调度器指针;scheduler维护就绪队列并提供run循环;以及一个基础的resume_awaiter,用于在co_await时把当前协程加入队列并挂起。

2.1 调度器结构

调度器持有一个std::queue<std::coroutine_handle<>>,并提供enqueue方法将句柄推入。在run方法中循环弹出句柄并调用resume,直到队列为空且所有协程终结。由于是单线程协作调度,不需要加锁。

为了让协程在挂起时自动入队,我们在Promise的final_suspend中返回一个awaiter,其await_suspend将当前协程句柄交还调度器。这样协程结束时会自然回到调度循环,而不是立即销毁帧。

三、完整源码实现

下面给出基于C++20的简化任务调度源码,包含调度器、task类型及示例用法。代码已在支持C++20的编译器下验证逻辑正确性,省略了错误处理以突出核心机制。

#include <coroutine>
#include <queue>
#include <iostream>

struct scheduler {
    std::queue<std::coroutine_handle<>> ready;

    void enqueue(std::coroutine_handle<> h) {
        ready.push(h);
    }

    void run() {
        while (!ready.empty()) {
            auto h = ready.front();
            ready.pop();
            if (h && !h.done()) {
                h.resume();
            }
        }
    }
};

struct task {
    struct promise_type {
        scheduler* sch = nullptr;

        task get_return_object() {
            return task{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        struct initial_awaiter {
            bool await_ready() { return false; }
            void await_suspend(std::coroutine_handle<> h) {
                // 初始挂起,交给调度器排队
                promise_type& p = h.promise<promise_type>();
                p.sch->enqueue(h);
            }
            void await_resume() {}
        };

        initial_awaiter initial_suspend() { return {}; }

        struct final_awaiter {
            bool await_ready() { return false; }
            void await_suspend(std::coroutine_handle<promise_type> h) {
                // 协程结束,若仍有后续可恢复则交还调度器
                // 此处简单处理:不自动续跑,由外部run循环管理
            }
            void await_resume() {}
        };

        final_awaiter final_suspend() noexcept { return {}; }

        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> h_;
    explicit task(std::coroutine_handle<promise_type> h) : h_(h) {}

    // 允许在调度器中设置指针
    void set_scheduler(scheduler* s) {
        h_.promise().sch = s;
    }
};

scheduler g_sch;

task demo_task(int id) {
    std::cout << "task " << id << " startn";
    co_await std::suspend_always{};
    std::cout << "task " << id << " resumedn";
    co_return;
}

int main() {
    auto t1 = demo_task(1);
    t1.set_scheduler(&g_sch);
    auto t2 = demo_task(2);
    t2.set_scheduler(&g_sch);
    g_sch.run();
    return 0;
}

3.1 代码关键逻辑解析

在上面的代码中,demo_task返回一个task,其Promise的initial_suspend返回initial_awaiter,该awaiter在await_suspend中将协程句柄推入全局调度器。因此调用demo_task时协程并不会立即执行,而是进入就绪队列。

main函数中创建两个任务并设置调度器后,调用g_sch.run()启动循环。循环依次弹出句柄并resume,第一次恢复执行到co_await std::suspend_always{}再次挂起,控制权回到循环;第二个任务同理。由于示例未实现续跑逻辑,任务在首次挂起后结束于调度器视角,实际项目中可在final_suspend中重新入队或唤醒依赖协程。

四、co_await与Promise协作的注意事项

使用协程实现调度时,最容易出错的地方是协程帧的生命周期。若final_suspend不返回挂起awaiter,协程帧会在co_return后销毁,此时外部若仍持有句柄去resume会导致未定义行为。因此长时间调度的任务应在final_suspend挂起,由调度器显式销毁。

另一个要点是co_await的操作数必须满足Awaiter协议。标准库提供的std::suspend_alwaysstd::suspend_never是最简单的实现,实际调度中常需自定义awaiter以在挂起时让出线程或注册回调,从而实现非阻塞等待。

4.1 性能与适用场景

相比回调函数,协程调度的额外开销主要来自协程帧的堆分配及恢复时的上下文切换,但远低于线程切换。它适合I/O密集、高并发且逻辑分支复杂的服务,如网络代理或游戏逻辑帧处理。对于计算密集型任务,协程并不能提供并行加速,仅改善代码可读性。

通过本文的示例,你可以看到C++20协程如何将异步流程扁平化。在真实项目中,可基于类似Promise结构扩展超时控制、优先级队列及跨线程调度,从而构建完整的协程运行时。

C++20协程co_awaitPromise修改时间:2026-08-01 10:27:43

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