You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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!

原因解析

其实这里的关键是两个点:

  1. Mutex本身是线程安全的:它内部通过原子操作管理锁的状态,所以即使不同系统线程中的协程调用它,也能正确实现互斥。
  2. 测试场景没有挂起点:你在withLock里的代码只是简单的counter +=1,没有任何会触发协程挂起的操作(比如delay、网络请求等)。这种情况下,协程会一直占用当前线程执行,不会释放线程,看起来就像线程被“阻塞”了,但这和系统级锁的线程阻塞完全是两回事。

如果在锁内加入挂起点,就能看到Mutex和系统级锁的核心差异:

suspend fun test() {
  mutex.withLock {
    counter = counter + 1
    delay(1) // 加入挂起操作
  }
}

此时两个线程的协程会交替执行,线程不会被一直阻塞,总耗时会接近100000ms(而不是系统级锁的200000ms)——因为持有锁的协程挂起时,会释放当前线程,让线程可以去执行其他协程,这才是Mutex作为协程同步工具的核心特性。

简单来说,Mutex不是为系统线程设计的同步工具,但它的线程安全特性让它在跨线程的协程场景下也能工作;而文档强调的“不阻塞线程”,是指协程挂起时会释放线程,而非它不能在多线程环境下实现互斥。


内容的提问来源于stack exchange,提问作者user7858768

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:15:14