multithreadingkotlinkotlin-coroutinesdata-transfer

How to transfer data between threads in Kotlin


I have to write a code where creating two threads - S and W. Thread S performs switching with 1000ms delay from true to false and back. Thread W is waiting "true" from thread S and outputs to the console countdown from 30sec with 100ms delay and pauses his action when thread S is swiched back to false. The condition for the termination of the operation of the threads is the achievement 0 sec of the countdown.

Firstly, I`ve created two classes - ThreadW and ThreadS. In ThreadS I created a state switch between true and false

ThreadS.kt

class ThreadS() : Thread() {
    override fun run()
    {
        var isTrue:Boolean = true
        while (true)
        {
            if (isTrue)
            {
                Thread.sleep(1000)
                isTrue = false
                println(isTrue)
            }
            else
            {
                Thread.sleep(1000)
                isTrue = true
                println(isTrue)
            }
        }
    }
}

In the main file I created coroutine and creating a class instance of ThreadS then starts it

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

fun main() = runBlocking {
    var t2 = ThreadW()
    t2.start()
}

The problem is that I have to transfer value of my switcher to another Thread and don`t know how to do it correctly. I`ve tried to solve this problem through channels but my attempts have been unsuccessful. Here is the code of ThreadW file where I`ve tried to solve it via channels

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

class ThreadW() : Thread() {
    override fun run() {
        val a = runBlocking {
            var timer : Int = 30
            var t1 = ThreadS()
            val channel = Channel<Boolean>()
            launch { t1.start() }
            val dataFromChannel = channel.receive()
            println(dataFromChannel)
            println("Done")
            if (dataFromChannel == true)
            {
                Thread.sleep(100)
                timer -= 1
            }
            println(timer)
        }
    }
}

Solution

  • Seems to me like isTrue and timer variables should be shared between two threads and exist outside of them. The classic problem you should be solving is possible race condition, when one thread updates the variable and the other reads the value at the same time resulting in obtaining stale value.

    There are multiple ways to prevent race condition. In this case using Atomic seems like the most simple solution.

    val isTrue = AtomicBoolean(true)
    val timer = AtomicInteger(30)
    

    Atomic types guarantee that every read/write operation will happen in single instruction and there will be no interaction with the value in the middle.

    fun main() {
        val isTrue = AtomicBoolean(true)
        val timer = AtomicInteger(30)
    
        val threadW = thread {
            while (timer.get() != 0) {
                println("ThreadW runs")
                if (isTrue.get()) {
                    println("Timer: $timer")
                    Thread.sleep(100L)
                    timer.getAndDecrement()
                }
            }
        }
    
        val threadS = thread {
            while (timer.get() != 0) {
                println("ThreadS runs")
                if (isTrue.get()) {
                    println("isTrue = true. Sleeping...")
                    Thread.sleep(1000L)
                    isTrue.set(false)
                    println("Set to false")
                } else {
                    println("isTrue = false. Sleeping...")
                    Thread.sleep(1000L)
                    isTrue.set(true)
                    println("Set to true")
                }
            }
        }
    }
    

    You can also do this using coroutines, just substitute thread with launch():

    fun main() {
        val isTrue = AtomicBoolean(true)
        val timer = AtomicInteger(30)
       
        launch(Dispatchers.Default) {
            while (timer.get() != 0) {
                println("ThreadW runs")
                if (isTrue.get()) {
                    println("Timer: $timer")
                    delay(100L) // Note that instead of Thread.sleep() coroutines use delay()
                    timer.getAndDecrement()
                }
            }
        }
       
        launch(Dispatchers.Default) {
            ...
        }
    }
    

    EDIT:

    Usually data is not transferred from one child thread to another, it is often shared and designed thread-safely, so different threads can access and modify it.