Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin local variable thread safety

So I was writing a unit test to test some multi-threading, and I want to know if this code is guaranteed to work as I would expect.

fun testNumbers() {
    var firstNumber: Int? = null
    var secondNumber: Int? = null
    val startLatch = CountDownLatch(2)
    val exec = Executors.newFixedThreadPool(2)

    exec.submit({
        startLatch.countDown()
        startLatch.await()
        firstNumber = StuffDoer.makeNumber()
    })
    exec.submit({
        startLatch.countDown()
        startLatch.await()
        secondNumber = StuffDoer().makeNumber()
    })
    while (firstNumber == null || secondNumber == null) {
        Thread.sleep(1)
    }
}

Specifically, is this method guaranteed to complete? firstNumber and secondNumber aren't volatile so does that mean the results set in those values from the exec threads might never be seen by the thread running the test? You can't apply volatile to local variables, so practically speaking it wouldn't make sense to me that you can't make function-local variables volatile if it might be necessary.

(I added Java as a tag because presumably the basic question is the same in Java.)

like image 750
CorayThan Avatar asked Dec 14 '22 00:12

CorayThan


1 Answers

When compiled with the Kotlin 1.1 RC compiler, the local variables in your code are stored in ObjectRefs, which are then used in the lambdas.

You can check what a piece of code is compiled to using the Kotlin bytecode viewer.

ObjectRef stores the reference in a non-volatile field, so there is indeed no guarantee that the program completes.

Earlier versions of Kotlin used to have a volatile field in the Ref classes, but this was an undocumented implementation detail (i.e. not something to rely on) that has eventually been changed in Kotlin 1.1. See this thread for the motivation behind the non-volatile captured variables.


As said in the issue description,

If a user is capturing a variable and handing it to other threads to work with, then it is a requirement of whatever concurrency control mechanism they are using to establish the corresponding happens-before edges between reads and writes to the captured variables. All regular concurrency mechanisms like starting/joining threads, creating futures, etc do so.

To make your example program correctly synchronized, it is enough to call .get() on the two Future instances returned from exec.submit { }, since Future provides happens-before guarantees:

Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.

val f1 = exec.submit { /* ... */ }
val f2 = exec.submit { /* ... */ }

f1.get()
f2.get()

// Both assignments made in the submitted tasks are visible now
assert(firstNumber != null) 
assert(secondNumber != null) 
like image 58
hotkey Avatar answered Jan 03 '23 16:01

hotkey