multithreadingkotlinconcurrencykotlin-coroutinessafe-publication

Thread-safe access to a Kotlin var from multiple threads


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?

Solution

  • 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.