An overview of how concurrency and mutability work in Kotlin/Native and strategies for how to get multi-threaded work done in that world using coroutines.
fun execute(block: suspend CoroutineScope.() -> Unit) // performs the block on another thread // and suspends, waiting for the result suspend fun <T> performAndWait(block: suspend CoroutineScope.() -> T): T
and concurrency rules in K/N • execute: coroutine worker pool • performAndWait: Doing work across threads Say Mean lambda lambda closure lambda block lambda
created, it’s “owned” by that thread • Accesses for that object are allowed on that thread only • 2 ways to move objects across threads Transfer Thread 1 Thread 2 SomeObj SomeObj Freeze
How do I scale creating objects inside a producer block? • Typical background work use cases are more complex worker.execute(TransferMode.SAFE, { Pair(1, 2) }) { println("bg result: ${it.first + it.second}") }
anyway (in this case) • Simplest approach in my view • We can more easily adopt our mental model to program this way val addThese = Pair(1, 2).freeze() worker.execute(TransferMode.SAFE, { addThese }) { println("bg result: ${it.first + it.second}") }
by mistake • Use .ensureNeverFrozen() Save yourself: init{ this.ensureNeverFrozen() } class Sample { val addThese = Pair(1, 2) Implicit this fun doWork() { val work = { println("bg result: ${addThese.first + addThese.second}") }.freeze() // do something with work } }
coroutine code • CAN pick a thread before running the coroutine code Coroutines + Worker Pool Native: coroutines require runBlocking fun doSync() { runBlocking { // SYNC } }
} fun performWork(work: suspend CoroutineScope.() -> Unit) { • Wrap in an API for running an arbitrary block in the background Coroutines + Worker Pool Step 2 Freeze the work closure Final step: add more workers!
holds a reference to a frozen object • Can freeze it • Can update it across threads package kotlin.native.concurrent public class AtomicReference<T>(private var value_: T) { // updates the reference public var value: T
Work Across package kotlin.native.concurrent public class AtomicInt(private var value_: Int) { public var value: Int public fun addAndGet(delta: Int): Int
<T> performAndWait(work: suspend CoroutineScope.() -> T): T Thread A parsing json doing something else performAndWait { // write to sqlite } Thread B write to sqlite Worker pool
T): T { // suspend the current coroutine while the work happens suspendCoroutine<T> { continuation -> // start the work somewhere in the pool pool.performWork { // perform the work, capturing error or success val result = runCatching { work() } // what to do with result? } } }
Mutex? (#462) Doing Work Across suspend fun <T> performAndWait(work: suspend CoroutineScope.() -> T): T { suspendCoroutine<T> { continuation -> pool.performWork { val result = runCatching { work() } // what to do with result? } } }
(safe to freeze) • In a another coroutine: while(result.value == null) { delay(50) } Doing Work Across suspend fun <T> performAndWait(work: suspend CoroutineScope.() -> T): T { suspendCoroutine<T> { continuation -> pool.performWork { val result = runCatching { work() } // what to do with result? } } }
T): T { val resultRef = AtomicReference<Result<T>?>(null) launch { while (resultRef.value == null) { delay(50) } // cross-thread work complete } suspendCoroutine<T> { continuation -> pool.performWork { val result = runCatching { work() } resultRef.value = result.freeze() } } } Update result on complete How do we resume?