操作系统线程调度是否影响std::sync::Mutex与tokio::sync::Mutex选型?
异步代码中同步Mutex与tokio::sync::Mutex的选择及常见疑问
先明确基础共识:
《tokio::sync::Mutex文档》提到:“与普遍认知相反,在异步代码中使用标准库的普通Mutex是可行的,且通常更受青睐。”
Stack Overflow上的回答进一步细化了不同场景下的选择逻辑:
- 如果需要在
.await调用期间持有锁,必须用异步互斥锁——多数同步互斥锁无法跨线程传递,线程安全的Future会直接在编译阶段报错。 - 当锁竞争激烈(大概率获取锁时它已经被占),比如多任务同步到线程池或有界队列的场景,优先用异步互斥锁。
- 如果是复杂或计算密集型的更新操作,这类操作应该放到阻塞线程池执行,此时用同步互斥锁更合适。
不过上述结论在单线程Tokio运行时里没问题,但在多线程运行时中,同步锁存在一个容易被忽略的问题:
当OS线程1上的任务A拿着同步锁被操作系统抢占后,OS线程2上的任务B被调度,它会不断自旋尝试获取锁,白白消耗CPU资源。而tokio::sync::Mutex不会有这个问题——任务B会调用await,然后被Tokio运行时换成其他能执行的任务,不会空耗CPU。
为什么同步互斥锁的这个潜在劣势很少被提及?
- 场景太特定:只有在「多线程异步运行时 + 持有同步锁的任务被OS抢占」这种组合下才会出现问题。单线程运行时根本不会有跨线程抢锁的情况;如果同步锁持有时间极短,被抢占的概率极低,实际影响可以忽略。
- 符合推荐用法的场景下不会暴露:按照前面的场景划分,竞争激烈的时候本来就推荐用异步锁,而同步锁的适用场景往往是「持有时间短、竞争低」的计算密集型操作(还会放到阻塞线程池,减少和异步任务的调度冲突),所以这种劣势在常规用法里很少出现。
- 开销感知不明显:同步锁的自旋开销是CPU短期消耗,很多时候不会表现为明显的业务性能瓶颈,相比之下,开发者更容易注意到异步锁调度带来的显性成本。
同步互斥锁的开销是不是普遍远小于异步互斥锁?
在同步锁的适用场景里(持有时间短、竞争低),是的,同步锁开销远低于异步锁:
- 同步锁的核心操作要么是用户态短时间自旋,要么是内核态线程阻塞,没有异步任务调度、唤醒的额外开销。
tokio::sync::Mutex作为异步锁,要维护等待队列、处理任务挂起和唤醒,这些都涉及Tokio运行时的调度逻辑,开销比同步锁基础操作高不少。
但一旦超出同步锁的适用场景——比如持有锁期间.await、锁竞争激烈——同步锁的开销会急剧上升:要么自旋浪费CPU,要么线程阻塞导致频繁上下文切换。这时候异步锁的优势就显现了:它能更高效利用线程资源,避免无意义的空耗。
内容的提问来源于stack exchange,提问作者AsynCrony
相关产品推荐
相关产品推荐

