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

Kotlin Native Concurrency Explained (Kotlinconf 2019)

Kotlin Native Concurrency Explained (Kotlinconf 2019)

Explain the Kotlin native concurrency model, best practices, and tools and libraries you should check out.

Kevin Galligan

December 05, 2019
Tweet

More Decks by Kevin Galligan

Other Decks in Programming

Transcript

  1. 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.
  2. This talk will cover •How the model works •My thoughts

    on concurrency and best practices •Libraries and future stuff (MT coroutines!)
  3. This talk will not cover •Me “selling” the model •Me

    complaining about the model •Debating various perspectives from the community
  4. public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  5. public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  6. public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  7. public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  8. public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  9. fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } kotlin.IllegalStateException: Illegal transfer state
  10. fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } execute wants to transfer someData between threads
  11. fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } checks result (and children) for external refs
  12. fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } local is still a ref till the end
  13. 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?
  14. Freeze • Recursively freezes everything • One-way (no “unfreeze”) •

    Data may be shared between threads Int String Float Float String Int MoreData val strData var width SomeData val moreData var count
  15. data class SomeData(val s:String, val i:Int) fun printStuff2() { val

    someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
  16. 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 }
  17. 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 }
  18. 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 }
  19. 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
  20. class CounterModel { var count = 0 fun countClicked() {

    background { count++ } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel
  21. class CounterModel { init { ensureNeverFrozen() } var count =

    0 fun countClicked() { background { count++ } } }
  22. expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean

    expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
  23. expect fun <T> T.freeze(): T expect fun <T> T.isFrozen(): Boolean

    expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
  24. Overview coroutine always bound to a single thread create with

    newSingleThreadContext on native that uses Worker Default has single background thread Main defined for Apple targets Windows/Linux ¯\_(ツ)_/¯
  25. Switching Threads use withContext (or equivalents) data passed in (or

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

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

    withContext(workerDispatcher) { println(someData) } }
  28. 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()}") }
  29. 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()}") }
  30. 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()}") }
  31. class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val

    sd = withContext(workerDispatcher){ DB.find(dbId) } println(sd) } init { ensureNeverFrozen() } }
  32. class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val

    capturedId = dbId val sd = withContext(workerDispatcher){ DB.find(capturedId) } println(sd) } init { ensureNeverFrozen() } }
  33. 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() } }
  34. 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() } }
  35. 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() } }
  36. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  37. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  38. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  39. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  40. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  41. init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }