Consider the following Kotlin code:
import kotlin.concurrent.thread
fun main() {
println("Press <Enter> to terminate.")
var interrupted = false
val worker = thread {
while (!interrupted) {
println("Working...")
Thread.sleep(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted = true
worker.join()
println("Terminated.")
}
as well the same example rewritten using coroutines:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
println("Press <Enter> to terminate.")
var interrupted = false
val worker = launch(Dispatchers.IO) {
while (!interrupted) {
println("Working...")
delay(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted = true
worker.join()
println("Terminated.")
}
Both examples will work in most cases, and yet both are broken, because, at the bytecode level, a boolean
variable accessed from more than a single thread is represented as a kotlin.jvm.internal.Ref.BooleanRef
which is not thread-safe.
It's worth mentioning that a Java compiler will require interrupted
to be final
and the identical Java code will simply fail to compile.
java.util.concurrent.atomic.AtomicBoolean
or kotlinx.atomicfu.AtomicBoolean
)?Based on Kotlin documentation
The first solution is a thread-safe data structure like AtmoicBoolean
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
println("Press <Enter> to terminate.")
val interrupted = AtomicBoolean()
val worker = thread {
while (!interrupted.get()) {
println("Working...")
Thread.sleep(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted.set(true)
worker.join()
println("Terminated.")
}
// coroutine way
fun main_2() = runBlocking {
println("Press <Enter> to terminate.")
val interrupted = AtomicBoolean()
val worker = launch(Dispatchers.IO) {
while (!interrupted.get()) {
println("Working...")
delay(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted.set(true)
worker.join()
println("Terminated.")
}
Second solution is Mutual exclusion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
fun main() = runBlocking {
println("Press <Enter> to terminate.")
var interrupted = false
val worker = launch(Dispatchers.IO) {
while (mutex.withLock { !interrupted }) {
println("Working...")
delay(1000L)
}
}
System.`in`.read()
println("Terminating...")
mutex.withLock { interrupted = true }
worker.join()
println("Terminated.")
}
I am just using two solutions for this problem in here you can find another solution
How can the above code (the 2nd fragment, which uses coroutines) be rewritten in the most portable way so it can target Kotlin/Multiplatform?