协程的承诺——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

目录
相关文章
|
3月前
|
缓存 NoSQL 网络协议
如何为我的网站或应用集成IP归属地查询功能?
本文为网站/应用集成IP归属地查询的落地指南:强调“取对IP”是前提(仅信可信上游、严滤私网),采用“本地+Redis缓存+在线API+硬超时熔断”架构,失败自动降级至省/国家;区分展示型与风控型模型,确保可解释、可审计、可回滚,并严守隐私合规红线。(239字)
325 13
|
3月前
|
数据采集 缓存 运维
IP查询工具如何评估IP负载?云上资源分配的实战方法
我们曾因P99延迟骤升盲目扩容无效,最终靠IP分桶定位到某云厂商ASN段的爬虫流量。IP查询工具不测性能,而是为请求打标签(ASN/代理类型/风险分等),结合监控数据精准识别“谁拖垮了系统”。分四类桶、设三条件、按优先级调度(分流>限流>扩容>封禁),离线缓存+二次验证,避免误伤。
|
3月前
|
自然语言处理 数据可视化 测试技术
在ModelScope上实现模型评测与压测服务化:PivotEval
魔搭推出PivotEval模型评测服务,一键完成模型效果与性能压测。无需搭建环境、下载数据集或写脚本,只需提供API地址并选择基准(如MMLU、GSM8K等),平台自动执行评测,生成交互式可视化报告,支持在线分享与本地复现。
590 4
在ModelScope上实现模型评测与压测服务化:PivotEval
|
3月前
|
新零售 移动开发 运维
直播带货平台开发需要多少钱?电商直播系统源码方案解析
直播电商爆发式增长,自建平台成新零售刚需。本文解析开发成本构成:直播技术、电商系统、管理后台三大模块;对比自研、定制、源码采购三种模式,成本从百万级降至数万元;强调稳定性、高并发与扩展性等关键技术能力,助力企业高效入局。
|
13天前
|
人工智能 监控 测试技术
银行业AI架构:从裸调API到六层技能体系
# 银行AI智能体架构实战:从单体到Skill协同的技术演进 ## 痛点:银行IT架构的三重困境 走在任何一家银行的科技部走廊里,你都能听到同样的叹息:系统又慢了、需求又排不上、监管又来查了。这不是某一家银行的困境,而是整个银行业IT架构的共性问题。我们把它拆解为三重困境。 **困境一:单体系
|
12天前
|
人工智能
2026 GOAI 世界人工智能开源大赛—新智基座 Agent Infra 赛道正式启动! ¥190万总奖池等你挑战!
2026 GOAI 世界人工智能开源大赛—新智基座 Agent Infra 赛道正式启动!¥190万总奖池等你挑战!
987 10
|
3月前
|
存储 数据采集 人工智能
2026年企业级BI系统建设方案:从数据孤岛到湖仓一体
在数字化转型深水区,数据孤岛严重制约价值释放。2026年企业级BI建设聚焦“湖仓一体”:以统一数据底座、全链路治理、AI原生分析为核心,打通系统壁垒、统一口径标准、激活全域数据,构建高可靠、全员可用的智能决策中枢。(239字)
|
3月前
|
人工智能 自然语言处理 前端开发
AI龙虾必备:4个做短视频的Agent Skills
AI龙虾必备:4个做短视频的Agent Skills
|
1月前
|
存储 人工智能 自然语言处理
知识库为谁而建 ?
随着 Agent 的逐步广泛应用,知识库的使用者正在从人变成 Agent。 知识库的设计逻辑、维护方式、甚至存在的意义,都需要重新思考。
628 10
知识库为谁而建 ?
|
1月前
|
Java Windows
JDK 8 安装与环境变量配置教程(jdk-8u121-windows-x64.exe 详细步骤)
本教程详解JDK 8u121 Windows 64位安装与配置:含管理员运行、路径选择、JAVA_HOME及Path环境变量设置,并通过java/javac -version命令快速验证,步骤清晰,适配Win10/Win11。