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.

58d1281770fe55a05a96600244ec8341?s=128

Kevin Galligan

December 05, 2019
Tweet

Transcript

  1. 3.
  2. 5.
  3. 6.
  4. 8.
  5. 9.
  6. 12.
  7. 14.

    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.
  8. 15.
  9. 17.
  10. 22.

    This talk will cover •How the model works •My thoughts

    on concurrency and best practices •Libraries and future stuff (MT coroutines!)
  11. 23.

    This talk will not cover •Me “selling” the model •Me

    complaining about the model •Debating various perspectives from the community
  12. 52.
  13. 54.

    public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  14. 55.

    public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  15. 56.

    public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  16. 58.

    public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  17. 60.

    public fun <T1, T2> execute( mode: TransferMode, producer: () ->

    T1, job: (T1) -> T2): Future<T2> { //... }
  18. 68.

    fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } kotlin.IllegalStateException: Illegal transfer state
  19. 69.

    fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } execute wants to transfer someData between threads
  20. 70.

    fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } checks result (and children) for external refs
  21. 71.

    fun printStuff2() { val someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE,

    { someData }) { println(it) }.result } local is still a ref till the end
  22. 72.

    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?
  23. 75.

    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
  24. 76.

    data class SomeData(val s:String, val i:Int) fun printStuff2() { val

    someData = SomeData("Hello ", 2) worker.execute(TransferMode.SAFE, { someData }) { println(it) }.result }
  25. 77.

    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 }
  26. 78.

    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 }
  27. 79.

    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 }
  28. 80.

    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
  29. 94.

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

    background { count++ } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel
  30. 105.

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

    0 fun countClicked() { background { count++ } } }
  31. 126.

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

    expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
  32. 127.

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

    expect fun <T> T.isNativeFrozen(): Boolean expect fun Any.ensureNeverFrozen()
  33. 133.
  34. 135.
  35. 136.

    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 ¯\_(ツ)_/¯
  36. 137.

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

    captured) is frozen data returned is frozen
  37. 139.

    suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  38. 140.

    suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  39. 141.

    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()}") }
  40. 142.

    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()}") }
  41. 143.

    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()}") }
  42. 145.

    class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val

    sd = withContext(workerDispatcher){ DB.find(dbId) } println(sd) } init { ensureNeverFrozen() } }
  43. 146.

    class DbModel(private val dbId:Int){ fun showDbStuff() = mainScope.launch { val

    capturedId = dbId val sd = withContext(workerDispatcher){ DB.find(capturedId) } println(sd) } init { ensureNeverFrozen() } }
  44. 147.

    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() } }
  45. 148.

    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() } }
  46. 149.

    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() } }
  47. 152.

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

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

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

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

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

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

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

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

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

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

    init { ensureNeverFrozen() mainScope.launch { query.asFlow() .map { assertNotMainThread() extractData(it)

    } .flowOn(ServiceRegistry.backgroundDispatcher) .collect { vt -> view?.let { it.update(vt) } } } }
  53. 159.
  54. 160.
  55. 161.
  56. 168.
  57. 180.