Kotlin document states that Mutex is for synchronizing co-routines, not System-level threads:
The key difference is that Mutex.lock() is a suspending function. It does NOT block a thread. - kotlin doc
However, when I tried to reproduce the behavior of Mutex not synchronizing system-level threads, I found that Mutex appears to block system-level threads. I am not following WHY it is appearing to synchronize system-level threads.
Code showing Mutex appearing to block system-level threads:
The following is the code example:
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("")
}
}
The output shows:
[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!
[🥇/tname:main/tid:1] part of output is thread name. We can see that the two non main threads: [[⓶/tname:Thread-0/tid:21], [⓷/tname:Thread-1/tid:22]] start at about the same time and finish at about the same time, showing us that they are running in parallel.
I am expecting the counter to be off from what it is expected to be. However, the counter is as expected. Appearing as co-routine Mutex running as synchronizing mechanism for system level threads.
Without mutex usage:
IF we remove the mutex usage in
suspend fun test() {
mutex.withLock {
counter = counter + 1
}
}
Function to be:
suspend fun test() {
// mutex.withLock {
counter = counter + 1
// }
}
We will see that the counter is off from what it is expected to be:
[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!
Back to question
Why does kotlinx.coroutines.sync.Mutex appear to block system level threads?
Mutexdoesn't block the thread, but it "blocks" the coroutine, so only one coroutine could enterwithLockat a time. By sayingMutexdoesn't block the thread we mean that while one coroutine waits on entering thewithLockblock, the same thread could execute another coroutine.runBlockingblock, which effectively create a single thread dispatcher using the calling thread.runBlockingwill block the calling thread waiting the coroutine to finish, its usage should be avoided if possible. You can try tod launch both coroutines in the samerunBlockingthread to see how it affects output.