Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin/Native Concurrency for Mobile Multiplatform

Kotlin/Native Concurrency for Mobile Multiplatform

If you're going to build shared Kotlin code with Kotlin/Native, you'll need to understand the concurrency and state model. In this talk, I give you the basics (and a bit more), plus next steps.

58d1281770fe55a05a96600244ec8341?s=128

Kevin Galligan

June 05, 2020
Tweet

Transcript

  1. Kotlin Native Concurrency Kevin Galligan

  2. None
  3. None
  4. Copenhagen Denmark KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan

  5. None
  6. Intro

  7. What is Kotlin Multiplatform?

  8. 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.
  9. modern

  10. Saner Concurrency? concurrency is hard

  11. None
  12. Kotlin Native model is controversial lots of disagreement

  13. JVM model is controversial many other platforms don’t let you

    do that
  14. JVM model is controversial many other platforms don’t let you

    do that
  15. JVM model is controversial many other platforms don’t let you

    do that
  16. You need to understand it libraries won’t hide all details

  17. important to relax

  18. Kotlin Native Rules

  19. 1) Mutable State = one thread thread confined

  20. 2) Immutable state = many threads no changes mean no

    problems
  21. kevin@touchlab.co @kpgalligan

  22. Internalize The Rules mutable = 1/immutable = many

  23. object BigGlobalState { var statusString = "" }

  24. Don’t manage concurrent state no “synchronized”, “volatile”, etc

  25. JVM Trusts You native does not

  26. Runtime Verification

  27. Do Things Differently not “losing” anything

  28. (jvm strict mode would be nice)

  29. You can break rules too just not a great idea

  30. Stages of grief skip ahead to acceptance

  31. Core Concepts

  32. Worker kn concurrency queue

  33. Similar to ExecutorService Handler/MessageQueue/Looper on Android

  34. Worker kn concurrency queue

  35. Mutability & State

  36. 1) Mutable State = one thread simple state is simple

  37. class SimpleState { private var i = 0 val currVal:

    Int get() = i fun doStuff(arg: Int) { i += arg } } fun main(){ val s = SimpleState() s.doStuff(22) s.doStuff(33) println("My val is ${s.currVal}") }
  38. 1) Mutable State = one thread you can transfer

  39. 1) Mutable State = one thread you can transfer

  40. 2) Immutable state = many threads not source immutable

  41. data class SomeData(val s:String, val i:Int) Mutable state?

  42. freeze() runtime immutability designation

  43. public fun <T> T.freeze(): T {...}

  44. Freeze • Recursively freezes everything • One-way (no “unfreeze”) •

    Data may be shared between threads Int String Float MoreData val strData var width SomeData val moreData var count
  45. Int String Float Float String Int MoreData val strData var

    width SomeData val moreData var count
  46. Crossing Threads

  47. fun background(block:()->Unit)

  48. fun background(block:()->Unit) block.freeze()

  49. fun printStuffBg() { val someData = SomeData("Hello ", 2) background

    { println(someData) } }
  50. fun printStuffBg() { val someData = SomeData("Hello ", 2) background

    { println(someData) } }
  51. fun printStuffBg() { val someData = SomeData("Hello ", 2) background

    { println(someData) } }
  52. fun printStuffBg() { val someData = SomeData("Hello ", 2) background

    { println(someData) } }
  53. class CounterModel { var count = 0 fun countClicked() {

    count++ } }
  54. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  55. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  56. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  57. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  58. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel
  59. Functions Have State careful what you capture

  60. class CounterModel { var count = 0 fun countClicked() {

    count++ save(count) } private fun save(count:Int) = background { saveToDb(count) } }
  61. Global State some special rules

  62. var someData = SomeData("a", 1)

  63. var someData = SomeData("a", 1) object GlobalObject { var data

    = SomeData("b", 2) }
  64. @ThreadLocal var someData = SomeData("a", 1) @ThreadLocal object GlobalObject {

    var data = SomeData("b", 2) }
  65. @SharedImmutable var someData = SomeData("a", 1) @ThreadLocal object GlobalObject {

    var data = SomeData("b", 2) }
  66. var someData:SomeData set(v:SomeData)… get():SomeData = …

  67. Why Special Rules? you can access it from anywhere

  68. Debugging Issues

  69. InvalidMutabilityException changing frozen state

  70. Why is this frozen() common question

  71. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  72. ensureNeverFrozen()

  73. class CounterModel { init { ensureNeverFrozen() } var count =

    0 fun countClicked() { count++ background { saveToDb(count) } } }
  74. Int String Float Int MoreData val strData var width SomeData

    val moreData var count
  75. Rethinking architecture intentional mutability

  76. Main Thread Other Thread (maybe?) Data Data Data Content Model

    iOS View Model
  77. This can get weird subtle things

  78. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  79. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  80. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  81. None
  82. This may be frustrating but should make you think

  83. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  84. Concurrent Mutability

  85. Atomics mutating frozen state

  86. class CounterModel { val count = AtomicInt(0) fun countClicked() {

    count.value++ background { saveToDb(count.value) } } }
  87. class CounterModel { val count = AtomicInt(0) fun countClicked() {

    count.value++ background { saveToDb(count.value) } } }
  88. val atom = AtomicReference(SomeData(“abc", 123))

  89. class SomeModel { val atom = AtomicReference(SomeData(“abc", 123)) fun update(sd:SomeData){

    atom.value = sd } init { freeze() } }
  90. Don’t use too many atomics AtomicRef can leak memory

  91. Worker dispatched state mutable state thread local

  92. State Thread IsolateState ??? State Container Data

  93. State Thread IsolateState ??? State Container ??? Data

  94. open class IsolateState<T : Any>

  95. open class IsolateState<T : Any> constructor(producer: () -> T)

  96. open class IsolateState<T : Any> constructor(producer: () -> T) fun

    <R> access(block: (T) -> R): R
  97. IsoArrayDeque IsoMutableCollection IsoMutableIterator IsoMutableList IsoMutableMap IsoMutableSet

  98. fun <K, V> IsoMutableMap<K, V>.checkFor2(first: K, second: K, ins: V)=

    access { map -> if (map.containsKey(first)) map.put(first, ins) else map.put(second, ins) }
  99. Objective-C/C/C++ just do what you want!

  100. Multiplatform & State

  101. Multiplatform State

  102. freeze() is native only

  103. expect fun <T> T.freeze(): T expect val <T> T.isFrozen: Boolean

    expect fun Any.ensureNeverFrozen() expect annotation class Throws
  104. freeze() on iOS threading rules apply

  105. Swift/Objc can’t be frozen be aware of concurrency issues

  106. Concurrency Libraries

  107. Official Native kotlinx.coroutines single-threaded

  108. ktor on native called on main thread

  109. multithreaded coroutines!

  110. multithreaded coroutines?

  111. Switching Threads use withContext (or equivalents) data passed in (or

    captured) is frozen data returned is frozen
  112. fun printStuffBg() { val someData = SomeData("Hello ", 2) background

    { println(someData) } }
  113. suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  114. suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  115. 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()}") }
  116. 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()}") }
  117. 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()}") }
  118. SomeData(s=Hello , Dogs!, i=3) I am frozen? true

  119. Flow, Channel, etc all freezable

  120. Main Thread Other Thread (maybe?) Data Data Data Content Model

    iOS View Model
  121. iOS Suspend Functions new for 1.4!

  122. Stately

  123. Stately (Probably) deprecated

  124. Stately v1! (ish)

  125. Modules • stately-common • stately-collections • stately-concurrency

  126. Modules • stately-isolate • stately-iso-collections • alpha. Feedback please!

  127. github.com/Autodesk/coroutineworker

  128. github.com/Badoo/reaktive

  129. Next Steps

  130. go.touchlab.co/kotlinlang

  131. go.touchlab.co/kndevto

  132. go.touchlab.co/knthreads

  133. JetBrains Links • https://github.com/JetBrains/kotlin-native/blob/ master/IMMUTABILITY.md • https://github.com/JetBrains/kotlin-native/blob/ master/CONCURRENCY.md • https://www.youtube.com/watch?v=nw6YTfEyfO0

  134. github.com/touchlab/KaMPKit

  135. Thanks! @kpgalligan touchlab.co