bigywater.tech
Rust · 101

select! 取消竞争

select! 看起来只是「谁先完成选谁」,但被选掉的那些 Future 会被直接 drop,一半做完的工作就此消失。这篇用一个会丢消息的例子说明「取消安全」是什么,再验证 select! 的公平性与 timeout 的取消行为。

drop
select! 中没赢的分支的结局
[2, 3]
取消不安全的 Future 被取消后,通道里剩下的消息(1 已丢失)
4910 对 5090
默认 select! 在两个分支都就绪时的选择分布(10000 次)

先到先得,其余取消

实测 · 本机执行输出
    快任务(30ms) 跑完了
    快任务(30ms) 被 drop(取消)
    慢任务(100ms) 被 drop(取消)
  select! 的结果:快任务(30ms)
  又过了 150 ms:慢任务既没有跑完也没有再打印,它在 select! 结束时就被取消了

Future 取消与 drop 的机制见Future 取消与 Drop。

取消不安全的例子

实测 · 本机执行输出
  20 ms 超时,read_then_work 被取消
  通道里还剩 [2, 3](消息 1 已经被 read_then_work 取走,取消时丢失了)

取消安全的写法

实测 · 本机执行输出
  100 ms 没有新消息,退出循环
  处理了 [1, 2, 3],一条都没丢

通道的另一种用法见异步 Channel。

公平性

实测 · 本机执行输出
  默认(随机):分支1 4910 次,分支2 5090 次(共 10000)
  biased;     :分支1 10000 次,分支2 0 次

timeout

实测 · 本机执行输出
    内部 Future 的局部变量被 drop(超时取消了它)
  timeout 返回 Err("deadline has elapsed")
  未超时的 timeout 返回 Ok("完成")

分步演示

select! 与取消
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。tokio 1.x(rt、time、macros、sync)
    • 没有验证:tokio 文档里各个 API 的「取消安全」标注是否与实测一致(只验证了 mpsc::Receiver::recv)、select! 里 else 分支、多个 select! 嵌套、futures::select!、tokio_util::sync::CancellationToken
    京ICP备2026067502号-1京公网安备11010502063487号