https://kotlinlang.org logo
#getting-started
Title
# getting-started
a

Alfred Lopez

06/24/2022, 5:35 PM
Hello, The attached file contains a class that tests access to a block of code protected by a mutex. The pattern is I only want the first coroutine to access the protected block, by rerouting all subsequent calls using a condition on mutex.isLocked. I’m getting inconsistent behavior: at times all coroutines can access the block and at other times, they get rerouted. With some experimentation, if I delay launching coroutines, then the expected behavior is observed, however, this is not a practical solution. Is there a best practice around using Mutex? Thanks!
Copy code
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class TestMutex {
    val mutex = Mutex()

    suspend fun createJobs() {
        val job1 = CoroutineScope(Default).launch {
            println("Job1")
            acquireMutex("Job1")
        }

        val job2 = CoroutineScope(Default).launch {
            println("Job2")
            acquireMutex("Job2")
        }

        val job3 = CoroutineScope(Default).launch {
            println("Job3")
            acquireMutex("Job3")
        }

        joinAll(job1, job2, job3)
        println("Delaying for a bit...")
        delay(1000)
        println("Done")
    }

    suspend fun createJobs2() {
        CoroutineScope(Default).launch {
            repeat(3) {
                CoroutineScope(Default).launch {
                    println("Job$it")
                    acquireMutex("Job$it")
                }
            }
        }.join()

        println("Delaying for a bit...")
        delay(1000)
        println("Done")
    }

    suspend fun acquireMutex(id : String) {
        if (!mutex.isLocked) {
            mutex.withLock {
                println("Got here...$id")
            }
        }
        else {
            println("I was directed to here...$id")
        }
    }
}

suspend fun main() {
    val testClass = TestMutex()

    println("First run...")
    testClass.createJobs()

    println("Second run...")
    testClass.createJobs2()
}
j

Joffrey

06/24/2022, 5:56 PM
This is not what mutex is for, though. Mutex protects concurrent access to the block of code, but nothing prevents 2 coroutines from running that block one after the other. Checking for
mutex.isLocked
is not what you want, because if other coroutines have run the block and then unlocked the mutex, you can still access the block in your current coroutine. What you could use instead is an atomic boolean for instance, which the first coroutine sets to true and others check
👍 1
a

Alfred Lopez

06/24/2022, 7:07 PM
Thanks. I’m pretty much getting the same result when I switch to AtomicBoolean, however, I have better success if I immediately set my boolean after the test. So it looks like to me that mutex.lock() takes quick awhile to set the state of the mutex. The reason I’m using a mutex is to suspend the other coroutines and redirect them after the lock is released. This code pattern is going to run in an JEE application, so I don’t think coroutines will fire that close together, but it is possible. So I guess I would have to live with those “concurrent misses”. Luckily, running the block of code several times doesn’t hurt. It’s just redundant in my use case. Thanks for the help.
j

Joffrey

06/24/2022, 7:15 PM
I don't quite understand how you set this up with an atomic boolean but you should always have exactly one execution of the block if you
getAndSet
it. Also I was talking about
kotlinx-atomicfu
's atomic boolean, not the JDK's - but it should be similar
a

Alfred Lopez

06/24/2022, 7:16 PM
Ah
j

Joffrey

06/24/2022, 7:16 PM
The reason I’m using a mutex is to suspend the other coroutines and redirect them after the lock is released
Then I must have missed something about your goal here. It seems what you want is that all coroutines wait for some sort of result, not just that the block is executed only once
👍 1
In this case you might consider atomically launching an
async
so you get a
Deferred
, and then subsequent coroutines could get the same deferred and await it if needed (replace
async
with
launch
and
Deferred
with
Job
if you don't need a value)
a

Alfred Lopez

06/24/2022, 9:50 PM
FYI I solved my problem by applying your suggested use of Mutext in a coarser context, where all coroutines need to execute the block. Thanks for your help!
6 Views