外观
第 5 节:join! 与 select!——等全部,还是等最快
症状
异步并发(第 16 章)里,"等"的方式选错了:该等全部的用了 select!(只等一个就放弃其余),或者该等最快的用了 join!(白白等最慢的)。
诊断
两个宏都是"同时跑多个 Future",但放弃条件完全不同:
join!:等全部——所有 Future 都要完成,结果一起拿(第 16 章三只小鸟)select!:等最快——第一个完成的胜出,其余当场取消
选错的后果:
rust
// 场景:查缓存和查数据库,哪个先回来用哪个(应该用 select!)
tokio::join!(cache_query, db_query); // 错!两个都要等,缓存白查了
// 场景:三只小鸟都要叼完才开饭(应该用 join!)
tokio::select! {
_ = bird1 => {},
_ = bird2 => {},
_ = bird3 => {},
} // 错!一只叼完就开饭,另外两只被取消!解药
解药一:需要"全部结果" → join!:
rust
let (a, b, c) = tokio::join!(fetch_a(), fetch_b(), fetch_c());解药二:需要"最快结果" → select!(输掉的 Future 会被取消):
rust
tokio::select! {
result = cache_query() => println!("缓存命中:{}", result),
result = db_query() => println!("数据库兜底:{}", result),
}解药三:想"最快结果但输了也别浪费" → select! 的手动 Future 版:把两个 Future 都拿在手里,谁先完成用谁,输的可以改派别的用场:
rust
let cache = cache_query();
let db = db_query();
tokio::pin!(cache, db);
tokio::select! {
result = &mut cache => { /* 用缓存;db 还在手里,可以继续等或取消 */ }
result = &mut db => { /* 用数据库 */ }
}预防
- 一问定乾坤:"这些任务都要结果,还是只要最快的?"——要全部
join!,要最快select! select!的取消是"放弃":输掉的 Future 停止推进(第 16 章:Future 可以放弃)——有副作用的任务(写文件、扣费)别放select!,或设计成"取消安全"- "谁先到用谁"的经典场景:双域名容灾、缓存 vs 直查、等待事件或超时(第 6 节 timeout 是
select!的亲兄弟:等"任务"和"计时器"谁先到) join!的元组:结果一次拿齐,数量固定——数量不定时用FuturesUnordered(futures 库)或逐个 await