Communication Break Down

Communication Break Down

Coroutines are great, I think we all agree on that. But as the async, and possibly parallel, programming becomes easier the risk of sharing mutable variables between coroutines arises.

When the boundaries are abstracted away we should rely on safe ways to communicate between our coroutines.

In this session, I will go through safe and unsafe ways of communication between different coroutines, and why it's not safe to think of them purely as threads.

0297b9b4bfd45c0f9c6c52bf696b7735?s=128

Bob Dahlberg

February 02, 2020
Tweet

Transcript

  1. Communication Break Down Bob Dahlberg Mobile Lead Developer

  2. Coroutines

  3. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be.
  4. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be. fun main() = runBlocking<Unit> { repeat(100_000) { launch { !// creates a coroutine println(“On thread !-> $thread”) } } }
  5. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be. repeat(100_000) { launch { !// creates a coroutine println(“On thread !-> $thread”) } }
  6. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be. repeat(100_000) { thread { !// creates a thread println(“On thread !-> $thread”) } }
  7. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be. repeat(100_000) { launch(Dispatchers.Default) { !// 8 threads println(“On thread !-> $thread”) } }
  8. Lightweight threads Coroutines “Think of them as lightweight threads” What

    do we mean by lightweight? Should we treat them as threads? Because they might be. repeat(100_000) { launch(Dispatchers.IO) { !// 84 threads println(“On thread !-> $thread”) } }
  9. Lightweight threads Coroutines How about thread safety?

  10. Lightweight threads Coroutines How about thread safety? var i =

    0 repeat(100_000) { launch(Dispatchers.Default) { i += it } } println("Result !-> $i”)
  11. Lightweight threads Coroutines How about thread safety? var i =

    0 repeat(100_000) { launch(Dispatchers.IO) { i += it } } println("Result !-> $i”)
  12. Lightweight threads Coroutines How about thread safety? var i =

    0 repeat(100_000) { launch { i += it } } println("Result !-> $i”)
  13. Lightweight threads Coroutines How about thread safety? var i =

    0 launch(Dispatchers.Default) { repeat(100_000) { launch { i += it } } println("Result !-> $i”) }
  14. Lightweight threads Coroutines How about thread safety?
 @Volatile var i

    = 0 launch(Dispatchers.Default) { repeat(100_000) { launch { i += it } } println("Result !-> $i”) }
  15. Treat them as threads? Coroutines So treat them as threads

    and we are fine?
  16. Treat them as threads? Coroutines So treat them as threads

    and we are fine? launch(Dispatchers.Unconfined) { println(“A1. On thread $thread”) delay(200) println(“A2. On thread $thread”) } A1. On thread main A2. On thread worker-1
  17. Treat them as threads? Coroutines fun main() = runBlocking<Unit>{ launch(Dispatchers.IO)

    { println(“A1. On thread $thread”) switchContext() println(“A2. On thread $thread”) } } suspend fun switchContext() { withContext(Dispatchers.Default) { println(“B1. Switching $thread”) } } A1. On thread worker-1 B1. Switching worker-1 A2. On thread worker-1 A1. On thread worker-1 B1. Switching worker-3 A2. On thread worker-3
  18. Treat them as threads? Coroutines val local = ThreadLocal<String?>() fun

    main() = runBlocking<Unit>{ launch(Dispatchers.IO) { local.set("IO") println(“A1. ${local.get()}”) switchContext() println(“A2. ${local.get()}”) } } suspend fun switchContext() { withContext(Dispatchers.Default) { println(“B1. ${local.get()}”) local.set("Default") } } A1. IO B1. IO A2. Default A1. IO B1. null A2. Default
  19. Treat them as threads? Coroutines So treat them as threads

    and we are fine?
  20. Treat them as coroutines! Coroutines So treat them as threads

    and we are fine? Nope, treat them as coroutines!
  21. Treat them as coroutines! Coroutines Excellent example from Dan Lew

    (blog.danlew.net)
 @Synchronized fun criticalSection() { println(“Starting!") Thread.sleep(10) println("Ending!") } repeat(2) { thread { criticalSection() } } Starting! Ending! Starting! Ending!
  22. Treat them as coroutines! Coroutines Excellent example from Dan Lew

    (blog.danlew.net)
 @Synchronized suspend fun criticalSection() { println("Starting!") delay(10) println("Ending!") } repeat(2) { launch(Dispatchers.Default) { criticalSection() } } Starting! Starting! Ending! Ending!
  23. Treat them as coroutines! Coroutines Excellent example from Dan Lew

    (blog.danlew.net)
 @Synchronized fun criticalSection() { println("Starting!") Thread.sleep(10) println("Ending!") } repeat(2) { launch(Dispatchers.Default) { criticalSection() } }
  24. Let’s communicate coroutine-style

  25. Deferred Communication Deferred is a non-blocking cancelable future.

  26. Deferred Communication Val result: Deferred<Response> = async { fetchChannels() }

    println(“Deferred !-> ${result.await()}”)
  27. Deferred Communication val result = async { delay(2000) } val

    result2 = async { delay(1000) } println(“Deferred !-> ${result.await()}”) println(“Deferred !-> ${result2.await()}”)
  28. Deferred Communication data class Temp(var name: String) val result =

    CompletableDeferred<Temp>() launch(Dispatchers.Default) { val temp = Temp("Bob") result.complete(temp) temp.name = "Charlie" } val temp = result.await() println("Deferred !-> $temp") Deferred !-> Temp(name=“Bob”) or Deferred !-> Temp(name=“Charlie”)
  29. Deferred Communication data class Temp(val name: String) val result =

    CompletableDeferred<Temp>() launch(Dispatchers.Default) { result.complete(Temp("Bob")) delay(1) result.complete(Temp("Charlie")) } val temp = result.await() println("Deferred !-> $temp”) Deferred !-> Temp(name=“Bob”)
  30. Deferred Communication data class Temp(var name: String) val result =

    CompletableDeferred<Temp>() launch(Dispatchers.Default) { result.complete(Temp("Bob")) delay(1) result.complete(Temp("Charlie")) } launch(Dispatchers.IO) { val temp = result.await() println("Deferred !-> $temp”) }
  31. Channels Communication Channels provide a way to transfer 
 a

    stream of values.
  32. Channels Communication val channel = Channel<String>() launch(Dispatchers.Default) { channel.send("Bob") channel.send("Charlie")

    } println(“Get !-> ${channel.receive()}”) println(“Get !-> ${channel.receive()}”)
  33. Channels Communication val channel = Channel<String>() launch(Dispatchers.Default) { channel.send("Bob") println(“Get

    !-> ${channel.receive()}”) }
  34. Channels Communication val channel = Channel<String>( ) launch(Dispatchers.Default) { channel.send("Bob")

    println(“Get !-> ${channel.receive()}”) } 1
  35. Channels Communication Channel<String>(7) !// BUFFERED Channel<String>(Channel.UNLIMITED) Channel<String>(Channel.CONFLATED) Channel<String>(Channel.RENDEZVOUS)

  36. Channels Communication val channel = Channel<String>() launch(Dispatchers.Default) { channel.send("Bob") channel.send("Charlie")

    } println(“Get !-> ${channel.toList()}”)
  37. Channels Communication val channel = Channel<String>() launch(Dispatchers.Default) { channel.send("Bob") channel.send(“Charlie”)

    channel.close() } println(“Get !-> ${channel.toList()}”)
  38. Channels Communication Channels are synchronization primitives Let’s see where they

    excel
  39. Channels Communication suspend fun race(name:String): String { delay(nextLong(5000)) return name

    } val ch = Channel<String>() launch(…) { ch.send(race("Bob")) } launch(…) { ch.send(race(“Charlie")) } launch(Dispatchers.Default) { repeat(2) { println(“Name: ${ch.receive()}") } ch.close() } Name: Charlie | Name: Bob Name: Bob | Name: Charlie
  40. Channels Communication val ch = Channel<Int>() launch(Dispatchers.Default) { repeat(30) {

    ch.send(it) } ch.close() } repeat(3) { id !-> launch(Dispatchers.Default) { for(msg in ch) { println(“$id !-> $msg”) } } } 0 !-> 0 2 !-> 1 1 !-> 2 ……
  41. Channels Communication val ch = produce { repeat(30) { send(it)

    } } repeat(3) { id !-> launch(Dispatchers.Default) { ch.consumeEach { println("$id !-> $it") } } } 0 !-> 0 2 !-> 1 1 !-> 2 ……
  42. Mutex Communication Mutex - Kotlins mutual exclusion

  43. Mutex Communication var i = 0 repeat(100_000) { launch(Dispatchers.Default) {

    i += it } } println("Result !-> $i”)
  44. Mutex Communication val mutex = Mutex() var i = 0

    repeat(100_000) { launch(Dispatchers.Default) { mutex.withLock { i += it } } println("Result !-> $i”)
  45. Mutex Communication val mutex = Mutex() suspend fun criticalSection() {

    mutex.withLock { println("Starting!") delay(10) println("Ending!") } } repeat(2) { launch { criticalSection() } }
  46. Flow Communication Flow - reactive streams contender

  47. Flow Communication val example: Flow<Int> = flow { for(i in

    1!..10) { emit(i) } } example.collect { println("Value !-> $it") } Value !-> 1 Value !-> 2 … Value !-> 10
  48. Flow Communication val example = flow { for(i in 1!..10)

    { emit(i) } } example.filter { it !>= 5 } .map { it * 2 } .collect { println("Value !-> $it") } Value !-> 10 Value !-> 12 … Value !-> 20
  49. Flow Communication val example = (1!..10).asFlow() example.filter { it !>=

    5 } .map { it * 2 } .collect { println("Value !-> $it") } Value !-> 10 Value !-> 12 … Value !-> 20
  50. Flow Communication val example = flow { for(i in 1!..10)

    { println(“Flow on !-> ${thread()}”) emit(i) } } example.filter { it !>= 5 } .map { it * 2 } .collect { println(“Collect on !-> ${thread()}”) } Flow on !-> main Collect on !-> main
  51. Flow Communication val example = flow { for(i in 1!..10)

    { println(“Flow on !-> ${thread()}”) emit(i) } }.flowOn(Dispatchers.Default) example.filter { it !>= 5 } .map { it * 2 } .collect { println(“Collect on !-> ${thread()}”) } Flow on !-> worker-1 Collect on !-> main
  52. Flow Communication val example = flow { for(i in 1!..10)

    { println(“Flow on !-> ${thread()}”) emit(i) } }.flowOn(Dispatchers.Default) example.filter { it !>= 5 } .map { println(“Map on !-> ${thread()}”) it * 2 } .collect { println(“Collect on !-> ${thread()}”) } Flow on !-> worker-1 Map on !-> main Collect on !-> main
  53. Flow Communication val example = flow { for(i in 1!..10)

    { println(“Flow on !-> ${thread()}”) emit(i) } } example.filter { it !>= 5 } .map { println(“Map on !-> ${thread()}”) it * 2 } .flowOn(Dispatchers.Default) .collect { println(“Collect on !-> ${thread()}”) } Flow on !-> worker-1 Map on !-> worker-1 Collect on !-> main
  54. Flow Communication val example = flow { for(i in 1!..10)

    { println(“Flow on !-> ${thread()}”) emit(i) } } example.filter { it !>= 5 } .flowOn(Dispatchers.IO) .map { it * 2 } .flowOn(Dispatchers.Default) .collect { println(“Collect on !-> ${thread()}”) }
  55. Flow Communication val example = flow { withContext(Dispatchers.Default) { for(i

    in 1!..10) { emit(i) } } } example.filter { it !>= 5 } .map { it * 2 } .collect { println(“Collect on !-> ${thread()}”) } Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
  56. Thank you!

  57. Questions? Bob Dahlberg bob@qvik.com medium.com/dahlbergbob @mr_bob The deck is available

    on: https://speakerdeck.com/bobdahlberg