Kotlin-native: Multithreading

Created on 28 Mar 2018  路  10Comments  路  Source: JetBrains/kotlin-native

Hello,

I was wondering if there are any planned upcoming releases that would support multithreading with Kotin objects here. I read here that multithreading is not currently supported with Kotlin Native. The specific situation I find myself in is using Konan to produce an iOS framework, setting a variable within a Kotlin companion object, and accessing this set variable from the utility thread. This variable is set once at the beginning of the app and then treated as readonly, but on the utility thread it is of course unset and crashes the app with an uninitialized exception.

Thank you

help wanted question

All 10 comments

Kotlin's internal memory management uses thread locals for initializing all backing memory for each Kotlin object, so any top level variable references are not shared across threads. Those companion objects will have their own copy in each thread, once they are accessed and initialized.

However a way to workaround this is to use lambdas. Executing a lamdba created from a primary thread and then executed on a different thread will retain the variable references locally, so a thread can then manipulate variables from another thread within the scope of the lambda function. This is a more flexible way than using the workers concept, but you'd still have to be careful about thread safety when changing mutable variables between threads.

BTW, you must call initRuntimeIfNeeded() from the cinterop package at the beginning of any new thread execution, otherwise you will get memory exceptions.

Does this mean that it will not be possible to access concurrently (from different threads) heap allocated memory (like you can do in JVM, without any transfer or freeze to read only)? There are even some effort to get to kotlin atomic values.

@barsan-ds The official documentation recommends using the utilities in the worker package to migrate objects between threads in a thread safe manner, regardless of how their memory was allocated (heap, memscoped, etc). My alternative suggestion is to use lambdas, however this method is not 100% foolproof thread-safe and is a YMMV solution. I believe the reference counting implementation is not atomic for non-frozen memory (take a careful look at Memory.cpp for more details) so you'd have to use this sparingly to avoid memory leaks/memory exceptions. It works if you limit the scope of concurrent access to a variable by serially executing the critical sections between the threads. The memory does not have to be transferred or frozen for this work, based on my experiments.

Thank you @J-Rojas for taking time to explain those details. There is one thing I didn't understand and is about "serially executing the critical sections".

My understanding is that you have to capture the variable you operate on, into a lambda and actually execute that lambda in different workers/threads. In that lambda you will have to take care to avoid racing conditions. Is it correct? And what about classes that reference other classes (For example a node of a tree have multiple children in the form of var attributes. Eg: var leftChild: Node and var rightChild: Node). Does this trick work (will the lambda capturing a parent be able to edit its children)?

@barsan-ds The easiest method to avoiding race conditions is to serially execute any lambdas that read/write the locally captured references. For example, if you are using a background thread to read from a database and return results to a primary thread, you can allocate the reference variable for the results in the primary thread, capture the results variable in the lambda, query the database and make changes to the results reference in the background thread, then schedule to execute a second lambda on the primary thread to consume the results after the background thread is complete with its work. This will offer the least likelihood of race conditions with carefully planned execution. I haven't tried the official workers provided by K/N but they seem to use a similar lambda execution concept with the extra requirements of having to transfer objects explicitly.

Based on my observations, any references to a Kotlin object captured by a parent reference will be available for modification within the lambda. For example a tree will be able to access/modify any node references if the root node is captured.

Hi all, do you have any samples where multithreading is handled on iOS? The iOS samples I found so far don't involve threading (mpp, calculator). Since I can't seem to use Workers from inside Swift code, I'm not sure how I should proceed with accessing an object created in a thread X from a thread Y.

For example, if my Kotlin/Native object producer was created in the main queue but then I access it from a background queue, it will fail an assertion (https://github.com/JetBrains/kotlin-native/commit/81b710985663056329919b2b72a2926a0737f47b EnsureRuntimeInitialized)

DispatchQueue.global().async {
    DispatchQueue.main.async {
        self.producer.foo() // works
    }

    self.producer.foo() // assertion failure
}

But I guess the recommended solution is not to dispatch to the main queue everywhere.

Thanks a lot and great job so far!

@gobetti Re: your EnsureRuntimeInitialized error, did you note @J-Rojas helpful instruction above? One needs to call initRuntimeIfNeeded() at the initialization of a new thread.

Hi @chris-hatton! That's helpful, thanks! Is it safe to always call that when dispatching to some queue, though? Given that not necessarily that
implies a new _thread_. Thanks again!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

msink picture msink  路  4Comments

antanas-arvasevicius picture antanas-arvasevicius  路  4Comments

msink picture msink  路  4Comments

jonnyzzz picture jonnyzzz  路  4Comments

ghost picture ghost  路  4Comments