Copenhagen Denmark
KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN
@kpgalligan
KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan - - PowerPoint PPT Presentation
KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan Copenhagen Denmark Touchlab [Your Org Here] reach out if interested, obv kevin@touchlab.co @kpgalligan I still have a talk! and then multithreaded
Copenhagen Denmark
KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN
@kpgalligan
Touchlab
[Your Org Here]
reach out if interested, obv
kevin@touchlab.co @kpgalligan
🔦
I still have a talk!
and then…
multithreaded coroutines!
Intro
What is Kotlin Multiplatform?
kot·lin mul·ti·plat·form
/ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/
noun noun: kotlin multiplatform 1.optional, natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms.
modern
Saner Concurrency?
concurrency is hard
Kotlin Native model is controversial
lots of disagreement
JVM model is controversial
many other platforms don’t let you do that
JVM model is controversial
many other platforms don’t let you do that
JVM model is controversial
many other platforms don’t let you do that
This talk will cover
This talk will not cover
Also…
no “getting around” rules
You need to understand it
libraries won’t hide all details
important to relax
Kotlin Native Rules
1) Mutable State = one thread
thread confined
2) Immutable state = many threads
no changes mean no problems
kevin@touchlab.co @kpgalligan
Internalize The Rules
mutable = 1/immutable = many
var statusString = "🚁🎹🐷" }
Don’t manage concurrent state
no “synchronized”, “volatile”, etc
JVM Trusts You
native does not
Runtime Verification
Do Things Differently
not “losing” anything
(jvm strict mode would be nice)
You can break rules too
just not a great idea
Worker
kn concurrency queue
Similar to ExecutorService
Handler/MessageQueue/Looper on Android
Worker
job queue thread???
executeWorker
job queue thread???
executeWorker
job queue thread???
executeWorker
job queue thread???
Worker
job queue thread???
Worker
job queue thread???
Worker
job queue thread???
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }
Hello 🐷
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }
public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }
public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }
public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }
public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }
val worker = Worker.start() worker.execute(TransferMode.SAFE, {"Hello 🐷"}) { println(it) }.result
public fun <T1, T2> execute( mode: TransferMode, producer: () -> T1, job: (T1) -> T2): Future<T2> { //... }
producer: () -> T1
{"Hello 🐷"} (Unit)->String
data class SomeData(val s:String, val i:Int) fun printStuff2() { worker.execute(TransferMode.SAFE, {SomeData("Hello 🐷🐷", 2)}) { println(it) }.result }
data class SomeData(val s:String, val i:Int) {SomeData("Hello 🐷🐷", 2)}
(Unit)->SomeData
data class SomeData(val s:String, val i:Int) fun printStuff2() { worker.execute(TransferMode.SAFE, {SomeData("Hello 🐷🐷", 2)}) { println(it) }.result }
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
kotlin.IllegalStateException: Illegal transfer state
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
execute wants to transfer someData between threads
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
checks result (and children) for external refs
fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
local is still a ref till the end
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
Mutable state?
freeze()
runtime immutability designation
public fun <T> T.freeze(): T {...}
Freeze
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2) worker.execute(TransferMode.SAFE, { someData.freeze() }) { println(it) }.result }
data class SomeData(val s:String, val i:Int) fun printStuff2() { val someData = SomeData("Hello 🐷🐷", 2).freeze() worker.execute(TransferMode.SAFE, { }) { println(someData) }.result }
Worker.execute must take an unbound, non-capturing function or lambda
fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block) }
fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block) }
Worker.execute must take an unbound, non-capturing function or lambda
fun background(block:(Unit)->Unit){ worker.execute(TransferMode.SAFE, {}, block.freeze()) }
Worker.execute must take an unbound, non-capturing function or lambda
fun background(block:()->Unit){ worker.execute(TransferMode.SAFE, {block}) { it() } }
kotlin.IllegalStateException: Illegal transfer state
fun background(block:()->Unit){ worker.execute(TransferMode.SAFE, {block.freeze()}) { it() } }
fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }
fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }
fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }
class CounterModel { var count = 0 fun countClicked() { count++ } }
class CounterModel { var count = 0 fun countClicked() { background { count++ } } }
class CounterModel { var count = 0 fun countClicked() { background { count++ } } }
class CounterModel { var count = 0 fun countClicked() { background { count++ } } }
class CounterModel { var count = 0 fun countClicked() { background { count++ } } }
class CounterModel { var count = 0 fun countClicked() { background { count++ } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel
Functions Have State
careful what you capture
Global State
some special rules
val mainOnly = SomeData("a", 1)
val mainOnly = SomeData("a", 1)
val data = SomeData("b", 2) }
@ThreadLocal val mainOnly = SomeData("a", 1) @ThreadLocal
val data = SomeData("b", 2) }
@SharedImmutable val mainOnly = SomeData("a", 1) @ThreadLocal
val data = SomeData("b", 2) }
Why Special Rules?
you can access it from anywhere
Tips and New Stuff
Debugging Issues
living with the new model
InvalidMutabilityException
changing frozen state
class CounterModel { init { ensureNeverFrozen() } var count = 0 fun countClicked() { background { count++ } } }
Rethinking architecture
intentional mutability
Main Thread Other Thread (maybe?)
Data Data DataContent Model iOS View Model
Atomics
mutating frozen state
class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }
class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }
class CounterModel { val count = AtomicInt(0) fun countClicked() { background { count.value++ } } }
val atom = AtomicReference<SomeData>(SomeData("", 0))
class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }
class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }
class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }
class SomeModel { val atom = atomic(SomeData("abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }
Atomic-fu/Stately
Don’t use too many atomics
AtomicRef can leak memory
DetachedObjectGraph
transfer state
DetachedObjectGraph
transfer state
Worker dispatched state
mutable state thread local
State Thread State Worker ??? State Container
DataState Thread State Worker ??? State Container ???
DataMultiplatform State
freeze in common
freeze() is native only
expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
Multithreaded Coroutines
General Thoughts
what does this mean?
State Model Clarity
strict mode is sticking around
Library Development
was a blocker
KMP is more “real”
Disclaimers
just fyi
Overview
coroutine always bound to a single thread create with newSingleThreadContext
Default has single background thread Main defined for Apple targets
Windows/Linux ¯\_(ツ)_/¯
Switching Threads
use withContext (or equivalents) data passed in (or captured) is frozen data returned is frozen
fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) background { println(someData) } }
suspend fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) withContext(workerDispatcher) { println(someData) } }
suspend fun printStuffBg() { val someData = SomeData("Hello 🐷🐷", 2) withContext(workerDispatcher) { println(someData) } }
suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }
suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }
suspend fun printStuffForeground() { val someData = SomeData("Hello 🐷🐷", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }
SomeData(s=Hello 🐷🐷, Dogs!, i=3) I am frozen? true
class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val sd = withContext(workerDispatcher){ DB.find(dbId) } println(sd) } init { ensureNeverFrozen() } }
class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val capturedId = dbId val sd = withContext(workerDispatcher){ DB.find(capturedId) } println(sd) } init { ensureNeverFrozen() } }
class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }
class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }
class DbModel(private val dbId: Int) { fun showDbStuff() = mainScope.launch { val sd = loadDbInfo(dbId) println(sd) } private suspend fun loadDbInfo(id: Int) = withContext(workerDispatcher) { DB.find(id) } init { ensureNeverFrozen() } }
Flow, Channel, etc
all freezable
Main Thread Other Thread (maybe?)
Data Data DataContent Model iOS View Model
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it) } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
Community Libraries
Stately?
Stately v1 (Probably) deprecated
a lot of it, anyway
@Deprecated Stately Collections
@Discouraged Stately Collections
January
that whole “holidays” thing
github.com/Autodesk/coroutineworker
github.com/Badoo/reaktive
Next Steps
go.touchlab.co/knthreads
JetBrains Links
master/IMMUTABILITY.md
master/CONCURRENCY.md
go.touchlab.co/mtco
Kotlin Koans
for native and state
January
Try out MT Coroutines?
some assembly required
Build and deploy local
assuming no updates from kotlinx.coroutines
> git clone -b native-mt \ https://github.com/Kotlin/kotlinx.coroutines.git > cd kotlinx.coroutines/ > ./gradlew build publishToMavenLocal
Samples
from slides https://github.com/kpgalligan/MTCoroutines Droidcon app https://github.com/touchlab/DroidconKotlin/ (look for the kpg/mt_coroutines branch)
KMP evaluation kit
KaMP-Kit
go.touchlab.co/KaMP-Kit
Stickers!
#KotlinConf
THANK YOU AND REMEMBER TO VOTE
Kevin Galligan @kpgalligan
Thanks!
@kpgalligan touchlab.co
Thanks!
@kpgalligan touchlab.co
J
n t h e t e a m !