协程的承诺——C++20中最复杂特性的设计故事

简介: C++20引入了协程,这被认为是自C++11以来最复杂的语言特性,甚至比模板元编程和移动语义更难掌握。

C++20引入了协程,这被认为是自C++11以来最复杂的语言特性,甚至比模板元编程和移动语义更难掌握。协程的提案和标准化过程历时多年,经历了多次重大修改,最终版本充满了抽象概念和新的关键字。理解协程为什么被设计成这个样子,需要回顾它的目标、约束、以及与其他语言协程实现的比较。

协程的基本思想是函数可以挂起和恢复。普通的函数调用后,控制权完全交给被调函数,直到它返回;而协程可以在执行到某个点时主动让出控制权(挂起),然后在稍后的某个时间点从挂起点恢复执行。这种能力对于异步编程、生成器、流处理、以及状态机等模式至关重要。在C++20之前,实现异步代码的传统方式是回调地狱:发起一个异步操作,传入一个回调函数,操作完成后调用回调。这种模式在简单场景下可行,但随着嵌套层数增加,代码变得难以阅读和维护。Promise-Future模式在一定程度上缓解了这个问题,但仍然需要显式管理状态。协程承诺将异步代码写成同步风格,让编译器自动处理挂起和恢复的底层机制。
参考:https://oqmyh.cn/category/mingan-huli.html

C++协程的设计面临三个核心约束:
零开销原则:协程的挂起和恢复不应该比等价的回调代码慢。这意味着协程的状态不能在堆上随意分配,必须尽可能紧凑,并且上下文切换的成本必须极低。
与现有C++代码的兼容性:协程必须能够调用普通函数,普通函数也必须能够调用协程。协程不能要求特殊的运行时或垃圾回收。
灵活性:协程应该能够用于多种场景:生成器(yield值)、异步任务(返回future)、惰性求值、以及自定义的状态机。

这些约束共同塑造了C++协程的独特设计。与Python或JavaScript中协程是语言核心的一部分不同,C++协程是库设施——语言只提供了底层机制,而具体的行为由库实现者通过一组约定来定义。
参考:https://oqmyh.cn/category/kang-shuailao.html

C++协程的核心概念有三个:协程句柄、承诺对象和挂起点。

协程句柄是一个非拥有型的句柄,用于恢复协程或查询协程的状态。它类似于指针,但设计为不拥有协程帧的内存(协程帧包含协程的状态,如局部变量和挂起点)。协程句柄可以在协程外部使用,允许外部代码恢复协程或销毁协程。

承诺对象是协程的控制面。它定义了协程的行为:当协程挂起时应该发生什么?当协程返回一个值时应该发生什么?当协程抛出异常时应该发生什么?协程的返回类型决定了承诺对象的类型——编译器通过std::coroutine_traits来查找承诺类型。这种设计使得同一个协程语言机制可以用于多种场景:对于生成器,承诺对象实现yield_value方法;对于异步任务,承诺对象实现await_transform和unhandled_exception。

挂起点是协程中co_await、co_yield和co_return出现的位置。co_await是最通用的挂起机制,它等待一个可等待对象。可等待对象必须实现await_ready(是否已经就绪)、await_suspend(挂起时做什么)和await_resume(恢复时返回什么)这三个方法。这种设计允许库作者自定义挂起和恢复的行为——例如,挂起时可以将协程句柄添加到事件循环中,等待IO完成后再恢复。

co_yield是co_await的特化,用于生成器场景:它将一个值产出给调用者,然后挂起。co_return表示协程完成,类似于普通函数的return。
参考:https://oqmyh.cn/category/hufu-chengfen.html

C++协程设计中最有争议的部分是对称转移(symmetric transfer)。考虑一个协程A等待协程B的情况:当B完成时,控制权应该转移到A。在对称转移的支持下,B可以直接恢复A,而不需要通过外部调度器。C++20最初没有强制要求对称转移,这导致某些实现需要额外的内存分配和调度器调用。C++23引入了std::coroutine_handle::resume的对称版本,但编译器支持尚不普遍。

协程的内存分配是另一个性能关键点。每个协程实例需要一个帧来存储局部变量和挂起状态。编译器会计算帧的大小,然后调用operator new分配内存。对于频繁创建和销毁的协程,内存分配的成本可能很高。优化策略包括:使用自定义分配器、对帧进行池化、以及利用返回值优化(如果协程的状态可以嵌入到调用者的帧中)。C++23引入了对协程帧使用自定义分配器的标准支持。

协程与RAII的交互值得特别注意。协程的局部变量在挂起期间仍然存活,这意味着如果协程持有一个RAII资源(如锁或文件句柄),该资源会在整个挂起期间保持占用,可能导致死锁或资源耗尽。开发者在设计协程时需要注意这一点,避免在挂起点持有锁或其他独占资源。

协程的调试比普通函数困难得多。在挂起点处,协程的栈帧不是完整的——部分状态存储在协程帧中,部分在调用者的栈上。这使得传统的栈回溯工具无法显示协程的完整调用链。一些调试器(如最新版本的GDB和LLDB)已经添加了对协程的部分支持,但体验仍然远不如普通函数。

协程的标准化只是第一步。真正的变革将来自基于协程构建的库和运行时。例如:
std::generator(C++23)提供了一个标准的生成器协程类型,用于惰性序列。
异步IO库(如boost::asio、cppcoro)提供了与事件循环集成的可等待对象。
并发任务框架允许协程在不同线程上执行,并在完成时恢复。

与Rust的async/await相比,C++的协程设计更加底层和灵活。Rust的异步机制完全依赖于运行时(如Tokio),而C++协程可以在任何环境下工作,包括嵌入式系统和无分配的环境。这种灵活性是以复杂性为代价的——Rust开发者通常只需要写async fn和.await,而C++开发者需要理解承诺对象、协程句柄和可等待协议的细节。

对于大多数C++开发者来说,直接使用协程的原始机制是不明智的。更好的做法是使用已经封装好的协程库,只在必要的时候深入到底层。随着C++23和C++26对协程的完善,以及社区积累的最佳实践,协程的编程模型将逐渐变得更加友好和直观。
参考:https://oqmyh.cn

目录
相关文章
|
7月前
|
存储 数据采集 人工智能
2026年企业级BI系统建设方案:从数据孤岛到湖仓一体
在数字化转型深水区,数据孤岛严重制约价值释放。2026年企业级BI建设聚焦“湖仓一体”:以统一数据底座、全链路治理、AI原生分析为核心,打通系统壁垒、统一口径标准、激活全域数据,构建高可靠、全员可用的智能决策中枢。(239字)
|
7月前
|
数据采集 编解码 人工智能
从自回归到掩码生成:拆解零样本 TTS 在长视频配音场景下的技术路径
本文系统拆解零样本TTS在长视频配音中的工程落地路径:剖析自回归与非自回归范式取舍,详解掩码生成(mask-and-predict)这一新兴技术如何兼顾自然度、鲁棒性与效率;深入探讨零样本克隆、跨语种声纹一致性、画面对齐等关键工程挑战,并分享数据预处理、提示工程、合规风控等生产级实践经验。(239字)
431 9
|
7月前
|
自然语言处理 数据可视化 测试技术
在ModelScope上实现模型评测与压测服务化:PivotEval
魔搭推出PivotEval模型评测服务,一键完成模型效果与性能压测。无需搭建环境、下载数据集或写脚本,只需提供API地址并选择基准(如MMLU、GSM8K等),平台自动执行评测,生成交互式可视化报告,支持在线分享与本地复现。
1095 4
在ModelScope上实现模型评测与压测服务化:PivotEval
|
5月前
|
人工智能 JavaScript 编译器
限时免费:云效 AI 代码评审新增跨文件感知,改一处查全局
云效 AI 代码评审新增跨文件感知能力,免费开放。改了一个函数,AI 自动追踪所有调用方,提 MR 时就能发现那些藏在 Diff 之外的风险。实测召回率提升 19 个百分点。
|
6月前
|
测试技术 UED
网站加载慢?用KKCE解决测速问题指南
本文面向零基础用户,详解网站测速的准备工作(优化网络、选定核心页面、多次取均值)、标准操作步骤及结果解读,无需专业技术即可快速掌握测速方法,精准定位加载慢问题,有效提升用户体验与转化效果。(239字)
300 8
|
6月前
|
数据采集 存储 自动驾驶
数据治理是什么?数据治理与数据要素、数据资源、数据资产、数据管理的区别
本文厘清“数据要素、资源、资产、管理、治理”五大易混淆概念:要素是国家层面的新型生产要素;资源是原始数据集合;资产是确权入表的价值形态;管理是全生命周期执行;治理是顶层设计与权责体系。五者层层递进,构成数据价值闭环。
|
6月前
|
人工智能 自然语言处理 算法
王耀恒:绝大多数从业者,根本没有实现GEO能力的闭环验证
GEO不是纸上谈兵的知识,而是必须亲历策略、生产、分发、监测、审计全流程,并经算法迭代验证的实战能力。王耀恒,深耕GEO一年半,完成超3000小时闭环实践,拒绝二手认知与AI幻觉,专注打造真实可复现的AI时代信任基建。(239字)
|
7月前
|
JavaScript 前端开发 Java
编程语言选型困难症?这份指南帮你找到最合适的“工具
本文系统解析编程语言选型核心逻辑:以“执行方式”(编译型/C、解释型/Python、字节码型/Java)和“编程范式”(面向过程、面向对象、函数式)为两大主线,结合项目类型、团队能力、性能需求等五步实战法,助你避开盲目追新等常见误区,理性选择最适合的编程语言。
编程语言选型困难症?这份指南帮你找到最合适的“工具
|
7月前
|
数据采集 JavaScript 前端开发
告别空壳HTML!Node.js + Playwright + 代理IP 优雅抓取动态网页实战
本文详解Node.js+Playwright抓取动态网页的实战方案:针对React/Vue等框架渲染的SPA页面,结合代理IP(支持动态/固定转发模式)突破采集限制,并提供BrowserContext级代理配置、IP有效性验证、健壮重试机制及常见报错(407/429/403)应对策略,助你构建高可用工业级爬虫。
854 0

热门文章

最新文章