to terminate.") var interrupted = false val wo" /> to terminate.") var interrupted = false val wo" /> to terminate.") var interrupted = false val wo"/>

Thread-safe access to a Kotlin var from multiple threads

7.3k views Asked by At

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.

Questions

  1. What is the canonical way to rewrite the above code using just the standard library (i. e. w/o java.util.concurrent.atomic.AtomicBoolean or kotlinx.atomicfu.AtomicBoolean)?
  2. How can the above code (the 2nd fragment which uses coroutines) be rewritten in the most portable way, so that it can target Kotlin/Multiplatform?
1

There are 1 answers

0
Mehdi Yari On BEST ANSWER

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?


I don't have much experience in kotlin-multiplatform, but you can't use `Dispacher.IO` in Kotlin multiplatform because it's bound to the JVM, so if you’re using Kotlin/JavaScript or Kotlin/Native projects, you won’t be able to use it.