2

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?
Bass
  • 4,977
  • 2
  • 36
  • 82
  • Did you tried @Volatile annotation for thread solution? For the coroutine one i'll recommend you to look over StateFlow. – Animesh Sahu May 21 '20 at 12:14
  • 1
    @animesh-sahu that is common misconception that making a variable volatile solves concurrency problem. please read [this](https://kotlinlang.org/docs/reference/coroutines/shared-mutable-state-and-concurrency.html) – Mehdi Yari May 21 '20 at 12:20
  • 1
    @MehdiYari It's a misconception that it makes increment modifications atomic. Here it is only read from one thread and written from another, so `@Volatile` does solve the problem. But OP is asking for a cross-platform solution and Volatile is JVM-only. – Tenfour04 May 21 '20 at 13:26
  • “just the standard library (i.e. w/o `java.util.concurrent.atomic.AtomicBoolean`)” — but that class _is_ part of the Java standard library (as mentioned [here](https://stackoverflow.com/a/35521983/10134209)), so for Kotlin/JVM the answer would simply be to use that.  (This only a partial frame challenge, though, as it's not in Kotlin/Multiplatform.) – gidds May 21 '20 at 13:53

1 Answers1

5

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.
Fony Lew
  • 505
  • 4
  • 16
Mehdi Yari
  • 481
  • 3
  • 12