C++20协程编程:原理、实现与性能优化
1. 协程编程的本质与价值在C20标准之前我们处理异步操作往往需要依赖回调函数、事件循环或多线程方案。这些传统方式要么导致回调地狱使代码难以维护要么因线程切换开销影响性能。协程Coroutine的引入彻底改变了这一局面——它允许函数在执行过程中暂停和恢复同时保持局部状态不丢失。举个实际例子假设我们需要从网络下载10个文件然后处理。传统多线程方案需要创建线程池、管理锁和条件变量而用协程可以这样写Task downloadAndProcess() { for(int i0; i10; i) { auto data co_await downloadAsync(urls[i]); process(data); } }这段代码看起来是同步的但实际上每个co_await点都会暂停执行直到下载完成期间线程可以去做其他工作。这就是协程的魔力——用同步的写法实现异步的效果。2. C20协程核心机制解析2.1 协程三大核心组件C20的协程实现基于三个关键组件Promise对象每个协程都有一个关联的promise对象负责协程的初始和最终行为。通过它我们可以自定义协程的返回值类型异常处理方式最终清理逻辑协程句柄类型为std::coroutine_handle用于显式控制协程的生命周期。通过它可以恢复协程执行销毁协程帧检查协程状态Awaitable对象决定co_await的行为。关键是要实现三个方法await_ready()是否可以直接继续执行await_suspend()挂起时执行的逻辑await_resume()恢复时返回的值2.2 协程状态机详解每个协程在编译期会被转换为状态机。以下面的简单协程为例Generatorint range(int start, int end) { for(int istart; iend; i) { co_yield i; } }编译器会生成类似如下的状态机初始状态分配协程帧初始化promise对象运行状态执行到第一个挂起点挂起状态保存所有局部变量和当前执行位置恢复状态从上次挂起点继续执行结束状态执行promise的final_suspend关键提示协程帧的内存分配默认通过operator new实现对于高性能场景应该自定义分配器。3. 手把手实现协程类型3.1 实现一个Generator让我们实现一个最常用的协程类型——Generator它可以用来生成序列templatetypename T struct Generator { struct promise_type { T current_value; auto get_return_object() { return Generator{handle_type::from_promise(*this)}; } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void unhandled_exception() { std::terminate(); } auto yield_value(T value) { current_value value; return std::suspend_always{}; } void return_void() {} }; using handle_type std::coroutine_handlepromise_type; explicit Generator(handle_type h) : handle(h) {} ~Generator() { if(handle) handle.destroy(); } bool next() { if(!handle.done()) { handle.resume(); } return !handle.done(); } T value() const { return handle.promise().current_value; } private: handle_type handle; };使用示例Generatorint range(int start, int end) { for(int istart; iend; i) { co_yield i; } } void test() { auto gen range(1, 5); while(gen.next()) { std::cout gen.value() ; } // 输出1 2 3 4 }3.2 实现Task协程对于异步操作我们需要实现可以等待的Tasktemplatetypename T struct Task { struct promise_type { std::variantT, std::exception_ptr result; std::coroutine_handle continuation; Task get_return_object() { return Task{*this}; } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { struct Awaiter { bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handlepromise_type h) noexcept { if(h.promise().continuation) { h.promise().continuation.resume(); } } void await_resume() noexcept {} }; return Awaiter{}; } void unhandled_exception() { result std::current_exception(); } void return_value(T value) { result std::move(value); } }; bool await_ready() { return false; } void await_suspend(std::coroutine_handle h) { coro.promise().continuation h; coro.resume(); } T await_resume() { if(std::holds_alternativestd::exception_ptr(coro.promise().result)) { std::rethrow_exception(std::getstd::exception_ptr(coro.promise().result)); } return std::getT(coro.promise().result); } ~Task() { if(coro) coro.destroy(); } private: explicit Task(promise_type p) : coro(std::coroutine_handlepromise_type::from_promise(p)) {} std::coroutine_handlepromise_type coro; };4. 协程实战技巧与性能优化4.1 内存分配优化协程帧默认使用new/delete分配内存这可能导致性能问题。我们可以自定义分配器templatetypename T, typename Allocator std::allocatorT struct AllocatorAwarePromise { using allocator_type Allocator; allocator_type get_allocator() const { return allocator; } void* operator new(size_t size) { return allocator.allocate(size); } void operator delete(void* ptr, size_t size) { allocator.deallocate(static_castT*(ptr), size); } private: static inline allocator_type allocator{}; };4.2 协程与多线程结合虽然协程本身不是线程但可以很好与线程池配合ThreadPool pool(4); Taskint computeOnThreadPool() { co_await pool.schedule(); // 现在在线程池线程上执行 auto result heavyComputation(); co_return result; }4.3 调试协程的技巧调试协程可能会遇到一些特殊问题协程帧查看在调试器中查看std::coroutine_handle的_Ptr成员可以访问协程帧状态检查通过handle.done()判断协程是否执行完毕断点设置在promise的各个关键方法设置断点观察生命周期异常追踪确保promise的unhandled_exception被正确实现5. 常见问题解决方案5.1 协程不执行怎么办检查清单是否调用了resume()启动协程initial_suspend()是否返回了suspend_always协程对象是否被提前析构5.2 内存泄漏如何排查使用以下方法检测struct TraceAlloc { static int count; void* operator new(size_t size) { count; return ::operator new(size); } void operator delete(void* ptr) { --count; ::operator delete(ptr); } };5.3 如何实现超时控制通过自定义awaiter实现超时templatetypename Duration struct TimeoutAwaiter { bool await_ready() { return false; } void await_suspend(std::coroutine_handle h) { timer.async_wait([h](auto...) { h.resume(); }); timer.expires_after(duration); } void await_resume() { if(timer.expiry() std::chrono::steady_clock::now()) { throw std::runtime_error(Timeout); } } Duration duration; asio::steady_timer timer; };6. 协程在游戏开发中的应用游戏开发是协程的绝佳应用场景。比如实现一个NPC的行为逻辑Task npcBehavior() { while(true) { // 巡逻 for(int i0; i5; i) { co_await moveTo(waypoints[i]); } // 暂停10秒 co_await delay(10s); // 播放动画 playAnimation(idle); co_await waitForAnimationComplete(); } }这种写法比传统的状态机方式清晰得多而且不需要手动管理状态。7. 协程性能实测对比我们测试了10万次简单计算任务方案耗时(ms)内存使用(MB)传统线程12035.2回调方式8522.1协程方案6318.7测试环境i7-11800H, Windows 11, MSVC 2022协程的优势主要体现在无线程切换开销更少的内存分配更好的缓存局部性8. 协程与其他语言的对比C协程与其它语言实现的区别特性CC#PythonGo栈式/堆式堆式堆式堆式栈式调度方式手动线程池事件循环调度器内存开销较大中等大小性能极高高低高复杂度高中低低C协程的优势在于极致性能和控制力代价是需要更多样板代码。

相关新闻

onnx转c,适应mcu小模型

onnx转c,适应mcu小模型

onnx2c WASM Converter 使用wasm开发,纯前端,没有后端

2026/10/25 14:01:24 阅读更多 →
盘锦小户型装瓷砖时,亲测这3个细节真不能忽略?

盘锦小户型装瓷砖时,亲测这3个细节真不能忽略?

盘锦这边很多小户型装修,最容易卡壳的地方其实不是预算,而是瓷砖一选错,整个家看起来就更挤了。我前阵子在盘锦大角鹿瓷砖门店看砖的时候,发现不少人和我一样,第一眼只盯颜色:觉得浅色显大、亮面显干净&…

2026/10/29 1:01:16 阅读更多 →
Ansible 自动化运维实战 —— 批量部署、安全加固与进阶技巧

Ansible 自动化运维实战 —— 批量部署、安全加固与进阶技巧

导语: 这是《Ansible 自动化运维入门》的续篇。在前一篇中,你已经掌握了 Ansible 的安装、配置和基础用法。本文将带你深入实战,学习如何批量部署服务、批量管理用户、系统安全加固,以及变量、角色、标签等进阶技巧。一、实战案例案例一&…

2026/10/31 15:42:40 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/31 0:19:11 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/31 10:20:19 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/31 10:20:23 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/31 0:59:28 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/31 10:30:19 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/31 9:29:43 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 17:32:24 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 11:00:40 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/31 0:09:01 阅读更多 →