在 C++ 并发编程里,std::future::get()是"等一个结果",when_all是"等所有结果",而 std::experimental::when_any解决的是第三类问题:一组异步任务里,谁先就绪就先响应谁 。它来自 C++ Extensions for Concurrency(Concurrency TS),配合带延续(continuation)的 std::experimental::future使用,是"竞速执行 / 投机执行 / 超时控制 / 并行搜索"的核心组合算子。
一、它到底解决什么问题
传统写法要"等第一个完成",通常是:
-
起 N 个线程
-
用互斥量 + 条件变量抢第一个结果
-
再想办法取消或忽略其余任务
这套写法又重又容易写错。when_any的语义非常干净:
输入一组
future<T>/shared_future<T>,返回一个新future;只要其中任意一个输入就绪(有值或有异常),返回的 future 就就绪。
它不等所有人,也不阻塞式轮询,而是把"竞争完成"这件事变成一等公民。
典型场景包括:
-
多镜像源下载,取最快那个
-
并行搜索大数组,找到一个就处理
-
主任务 + 定时器竞速,实现异步超时
-
多个模型/算法同时跑,先出结果先采用(speculative execution)
二、两种重载:变参 vs 迭代器区间
1. 变参版本(编译期已知个数)
template<class... Futures>
future<when_any_result<tuple<Futures...>>>
when_any(Futures&&... futures);
-
每个参数可以是
future<T>或shared_future<T> -
类型可以不同,结果用
std::tuple保存 -
future被移动,调用后原 future 失效 -
shared_future被复制,原 shared_future 仍有效
2. 迭代器区间版本(运行期未知个数)
template<class InputIt>
future<when_any_result<vector<typename InputIt::value_type>>>
when_any(InputIt first, InputIt last);
-
输入必须是同类型的
future<R>或shared_future<R> -
结果用
std::vector保存 -
空区间(
first == last)会返回一个立即就绪的 future,index = size_t(-1),容器为空
三、返回类型:when_any_result 是什么
when_any不是直接把"胜者的值"吐出来,而是返回一个包裹结构:
template<class Sequence>
struct when_any_result {
std::size_t index;
Sequence futures;
};
-
index:第一个就绪的 future 在输入序列中的位置 -
futures:原输入 future 的集合(顺序不变)-
变参版本是
tuple -
区间版本是
vector
-
也就是说,when_any的返回值是:
future<when_any_result<tuple<future<int>, future<string>>>>
// 或
future<when_any_result<vector<future<int>>>>
这一点和 when_all很像,但语义不同:when_all关心"全部完成后的集合",when_any关心"谁先完成 + 原始集合还在不在"。
四、future 与 shared_future 的行为差异
when_any对两种输入的处理方式不同:
| 输入类型 | 调用后原对象 | 结果容器里 |
|---|---|---|
future<T> |
valid() == false |
future 被移动进去 |
shared_future<T> |
仍有效 | shared_future 被复制进去 |
所以:
-
如果你想"多个消费者共享结果",用
shared_future -
如果你做完
when_any就不再单独等某个任务,用future并std::move进去
错误示例:
std::experimental::future<int> f = async([] { return 42; });
when_any(f); // 错误:future 不能拷贝
when_any(std::move(f)); // 正确
五、和 .then() 搭配才是现代写法
Concurrency TS 的 future支持延续:
std::experimental::future<int> a = async([] { return computeA(); });
std::experimental::future<int> b = async([] { return computeB(); });
auto any_f = when_any(std::move(a), std::move(b));
any_f.then([](auto f) {
auto result = f.get(); // when_any_result<tuple<future<int>, future<int>>>
std::size_t idx = result.index;
if (idx == 0) {
int v = std::get<0>(result.futures).get();
} else {
int v = std::get<1>(result.futures).get();
}
});
关键点:
-
when_any本身不阻塞 -
返回的 future 就绪后,通过
.then()在"某个实现定义的上下文"里继续跑 -
胜者已经就绪,所以
result.futures里对应位置的.get()不会阻塞 -
其余 future 还在容器里,你可以继续等,也可以直接丢弃
六、经典场景 1:异步超时
std::experimental::future<Data> task = async(do_expensive_work);
std::experimental::future<void> timer = async(sleep_for, 100ms);
when_any(std::move(task), std::move(timer))
.then([](auto r) {
if (r.index == 1) {
// 定时器先就绪:超时
} else {
// 任务先完成:拿结果
auto data = std::get<0>(r.futures).get();
}
});
这就是"async timeout"的标准范式:不需要线程取消机制,也能实现"超过 N ms 就放弃"。
七、经典场景 2:并行搜索,找到一个就处理
把数据分块,每块一个 future:
std::vector<std::experimental::future<Result>> tasks;
for (auto&& chunk : chunks) {
tasks.push_back(async([&] { return search(chunk); }));
}
std::experimental::future<when_any_result<vector<future<Result>>>> f =
when_any(tasks.begin(), tasks.end());
f.then([](auto r) {
auto result = r.get();
Result hit = std::get<result.index>(result.futures).get();
process(hit);
});
注意:这里只解决"第一个命中可处理",不自动取消其余任务。真要取消,需要:
-
原子标志
stop_requested -
任务里周期性检查
-
或者用协程 / 执行器级 cancellation
when_any只负责"感知第一个完成",不负责"杀掉别人"。
八、when_any 与 when_all 的本质区别
| 维度 | when_all | when_any |
|---|---|---|
| 触发条件 | 全部就绪 | 任意一个就绪 |
| 返回内容 | 原 future 集合 | index + 原 future 集合 |
| 适用模型 | join / 汇总 | choice / 竞速 |
| 异常处理 | 各子 future 自己抛 | 子 future 可能带异常,"胜者"可能是异常 |
| 典型用法 | 多阶段流水线收尾 | 多源取最快、超时、投机执行 |
一个容易忽略的点:"胜者"不一定是成功结果 。如果第一个就绪的 future 是 exceptional 状态,when_any的返回 future 仍然会就绪,但你在 result.futures[index].get()时会拿到异常。
九、空输入与边界行为
-
变参版本不传参数:返回立即就绪的
future<when_any_result<tuple<>>>,index = size_t(-1) -
区间版本
first == last:返回立即就绪 的 vector 版本,index = size_t(-1) -
输入里有无效 future:
undefined behavior -
输入类型不一致(区间版本):编译失败;变参版本允许异构
十、为什么标准最终没把 when_any 放进 std?
Concurrency TS 里的 experimental::future+ then+ when_any/when_all是一套"未来异步模型"提案,但后来 C++ 的演化重心转向了:
-
std::async保持基础能力 -
Executors / P2300
std::execution -
C++20 协程
co_await -
when_any风格能力在 sender/receiver 里变成let_first / when_any类算法
所以今天你会在三处看到"when_any 精神":
-
std::experimental::when_any(历史 TS,部分库实现过) -
P2300 / stdexec:
when_any在 sender 世界重新出现 -
协程里:
co_await select(a, b)/ 自写 race 逻辑
十一、现代替代写法(C++20 协程思路)
如果不用 experimental,用协程可以写成:
task<int> race(task<int> a, task<int> b) {
auto ra = a;
auto rb = b;
co_await whatever_ready(ra, rb);
if (ra.done()) co_return co_await ra;
co_return co_await rb;
}
但底层语义和 when_any一致:竞争就绪,不是全部完成。
十二、一句话总结
std::experimental::when_any是"异步世界里的 select / race / first-completed-wins 原语":它把一组 future 收成一个新 future,第一个就绪就触发,返回"索引 + 原集合",本身不阻塞、不取消其他任务、不隐藏异常。会用 when_any,才算真正从"多线程"走到"异步组合"。