为何kotlinx.coroutines.sync.Mutex看似阻塞系统级线程?
为什么kotlinx.coroutines.sync.Mutex看起来能阻塞系统级线程?
Kotlin文档明确说明,Mutex是用于协程同步而非系统级线程同步的,核心区别在于Mutex.lock()是挂起函数,它不会阻塞线程。但实际测试时却发现,它似乎能在系统级线程间起到同步作用,这让人困惑。
测试代码(Mutex看似阻塞系统线程)
以下是测试用的代码:
package com.glassthought.sandbox import gt.sandbox.util.output.Out import com.glassthought.sandbox.util.out.impl.OutSettings import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.concurrent.thread val mutex = Mutex() var counter = 0 suspend fun test() { mutex.withLock { counter = counter + 1 } } val out = Out.standard(OutSettings(printCoroutineName = false)) val TIMES_TO_REPEAT = 100000 suspend fun main(args: Array<String>) { out.info("Starting on main thread") val t1 = thread { runBlocking { incrementInALoop("thread-1") } } val t2 = thread { runBlocking { incrementInALoop("thread-2") } } t1.join() t2.join() printResults() } private suspend fun incrementInALoop(threadName: String) { out.info("Starting execution on $threadName") repeat(TIMES_TO_REPEAT) { test() } out.info("Finished execution on $threadName") } private suspend fun printResults() { out.info("Counter : $counter") val expected = TIMES_TO_REPEAT * 2 out.info("Expected: $expected") if (expected == counter) { out.printGreen("All accounted!") out.println("") } else { out.printRed("NOT all accounted!") out.println("") } }
运行输出:
[elapsed: 18ms][🥇/tname:main/tid:1] Starting on main thread [elapsed: 46ms][⓶/tname:Thread-0/tid:21] Starting execution on thread-1 [elapsed: 46ms][⓷/tname:Thread-1/tid:22] Starting execution on thread-2 [elapsed: 250ms][⓶/tname:Thread-0/tid:21] Finished execution on thread-1 [elapsed: 250ms][⓷/tname:Thread-1/tid:22] Finished execution on thread-2 [elapsed: 255ms][🥇/tname:main/tid:1] Counter : 200000 [elapsed: 255ms][🥇/tname:main/tid:1] Expected: 200000 All accounted!
从输出的线程名称可以看到,两个非主线程几乎同时启动并结束,看起来Mutex起到了系统级线程同步的作用,计数器结果完全符合预期。
移除Mutex后的情况
如果把test()函数中的Mutex调用移除:
suspend fun test() { // mutex.withLock { counter = counter + 1 // } }
运行后计数器结果会不符合预期:
[elapsed: 18ms][🥇/tname:main/tid:1] Starting on main thread [elapsed: 43ms][⓶/tname:Thread-1/tid:22] Starting execution on thread-2 [elapsed: 43ms][⓷/tname:Thread-0/tid:21] Starting execution on thread-1 [elapsed: 51ms][⓷/tname:Thread-0/tid:21] Finished execution on thread-1 [elapsed: 51ms][⓶/tname:Thread-1/tid:22] Finished execution on thread-2 [elapsed: 53ms][🥇/tname:main/tid:1] Counter : 144993 [elapsed: 53ms][🥇/tname:main/tid:1] Expected: 200000 NOT all accounted!
原因解析
其实这里的关键是两个点:
- Mutex本身是线程安全的:它内部通过原子操作管理锁的状态,所以即使不同系统线程中的协程调用它,也能正确实现互斥。
- 测试场景没有挂起点:你在
withLock里的代码只是简单的counter +=1,没有任何会触发协程挂起的操作(比如delay、网络请求等)。这种情况下,协程会一直占用当前线程执行,不会释放线程,看起来就像线程被“阻塞”了,但这和系统级锁的线程阻塞完全是两回事。
如果在锁内加入挂起点,就能看到Mutex和系统级锁的核心差异:
suspend fun test() { mutex.withLock { counter = counter + 1 delay(1) // 加入挂起操作 } }
此时两个线程的协程会交替执行,线程不会被一直阻塞,总耗时会接近100000ms(而不是系统级锁的200000ms)——因为持有锁的协程挂起时,会释放当前线程,让线程可以去执行其他协程,这才是Mutex作为协程同步工具的核心特性。
简单来说,Mutex不是为系统线程设计的同步工具,但它的线程安全特性让它在跨线程的协程场景下也能工作;而文档强调的“不阻塞线程”,是指协程挂起时会释放线程,而非它不能在多线程环境下实现互斥。
内容的提问来源于stack exchange,提问作者user7858768
相关产品推荐
相关产品推荐

