背景#
C++ 的异步编程一直很别扭。
std::future / std::promise 用起来像在泥地里跑步:每次 .then() 都要堆分配加一把锁,std::function 做类型擦除后还要再来一次间接调用。这不是零开销原则,简直是反向优化。
2024 年 6 月,ISO C++ 委员会在圣路易斯会议上投票通过 P2300 std::execution 进入 C++26 工作草案。这个提案也叫 Senders & Receivers。它不是又一个异步库,而是 C++ 对结构化并发的一次标准级回答。
Eric Niebler 的比喻很准确:goto → for/while/if 是从非结构化编程到结构化编程;std::future/mutex → Senders/Receivers,是并发领域里的同类变化。
原理#
Sender:惰性计算描述#
Sender 是惰性的计算描述。它不执行任何东西,直到你把它连接到 Receiver。
| |
这里有三点:
just(X)创建一个 sender,它产生值 X| then(f)把 sender 和函数 f 组合成新 sendersync_wait是 consumer,它把 sender 连上 receiver,阻塞等待完成
这跟函数组合很像:just(X) | then(f) 等价于 f(X)。区别在于 sender 可以跨线程、跨调度器,并且严格不分配堆内存。
三个通道:值、错误、取消#
每个 sender 最终只会走三条路之一。和 std::future 相比,取消不是补丁,而是一等公民。
| 通道 | 含义 | 对应算法 |
|---|---|---|
set_value | 正常完成 | then, let_value |
set_error | 异常/出错 | upon_error, let_error |
set_stopped | 被取消 | upon_stopped, let_stopped |
Scheduler:执行位置#
Sender 不直接碰线程。任务在哪跑,由 Scheduler 决定:
| |
schedule(sch) 返回一个 sender,在 sch 指定的上下文中调度执行。中途想切换调度器,就用 continues_on。
when_all:并行编排#
f 和 g 并发跑,拿到两个结果后求和:
| |
when_all 是结构化并发的关键工具:多个 sender 并发执行,全部完成后打包传给下一个 then。不用手动 join,也不用自己加锁。
split():复用而不重复执行#
普通 sender 只能消费一次。如果同一个 sender 要喂给两条不同管线:
| |
split() 让 sender 可共享,内部处理多个 receiver 的连接关系。
一段最小 sender 例子#
完整例子:I/O 线程读数据,CPU 线程池处理,再写回连接。
| |
四个关键点:
starts_on(io_sch, ...)在 I/O 调度器上启动整条链continues_on在中途切换执行上下文,数据跟着 move 过去- 零堆分配:所有 lambda 类型编译期已知,不需要
std::function - 无锁:sender/receiver 协议保证同一时刻只有一处持有数据
对比 std::future#
同样的事用 std::future 写:
| |
.then() 不是标准 std::future 的成员。要实现类似链式调用,开发者得自己配 promise/future、管理工作线程、处理取消。每一层 then 都会带来:std::function 类型擦除(堆分配)、shared state 的原子引用计数、内部互斥锁防竞态。
Sender 把这些类型在编译期叠成嵌套结构体,然后通过 move 传递。这里的“零开销”不是口号,而是 ABI 层面没有 std::function 和共享状态那套东西。
实现现状#
| 项目 | 说明 | 状态 |
|---|---|---|
| NVIDIA stdexec | P2300 参考实现,header-only | 活跃维护,2.4k ⭐ |
| libunifex | Meta 的实验版本 | 已被 stdexec 取代 |
| HPX | 分布式 C++ 异步运行时 | 支持 sender/receiver |
| P3109 | C++26 额外 sender 设施规划 | 提案中 |
stdexec 是目前唯一活跃的完整实现。它在标准提案之外还提供 async_scope(结构化并发作用域)、task(coroutine 互操作)、io_uring 调度器、GPU 调度器等扩展。
今天就能做的事#
- 跑 hello world:clone stdexec,编译
examples/hello_world.cpp,观察 sender 管线的延迟执行特性 - 改写现有代码:找项目里一段用
std::async的地方,用stdexec::on(sch, just(X) | then(f))改写,对比性能 - 读 P2300R10 motivation 章节:不到 10 页,讲清楚了为什么
std::future这条路走不通
参考#
- P2300R10: std::execution:标准提案全文
- Senders/Receivers: An Introduction (ACCU Overload 184):Lucian Radu Teodorescu
- Senders/receivers in C++ (CompuTruthing):P2300 合著者立场说明
- NVIDIA stdexec:参考实现
- CppCon 2019: A Unifying Abstraction for Async in C++:Eric Niebler 演讲

