$30 off During Our Annual Pro Sale. View Details »

Concurrency with Coroutines and Actors

Concurrency with Coroutines and Actors

Through coroutines concurrency takes place in kotlin applications and new challenges appear. How do you modify state without exposing inconsistent data? How do you avoid deadlocks?
First you will get a short introduction to Kotlin's channels. Afterwards we will dive into the world of the Actor Model and point out some of the key ideas behind it. We will then apply this concept to kotlin and discuss the benefits and downsides.

by Nils Ernsting
presented on June 25, 2019 @Itemis

More Decks by Kotlin User Group Hamburg

Other Decks in Programming

Transcript

  1. A short dive into the world of actors in Kotlin

    Mastering concurrency with coroutines
  2. Coroutines are cool but.. How to gurantee reading consistent states

    all the time? Should I use synchronized blocks? How are errors handled and propagated? Is there an equivalent to thread local with coroutines? .. or monitors?
  3. About us Nils Ernsting Holisticon AG is a management and

    IT consultancy based in Hamburg. With a holistic consulting approach, we support our customers in their development projects on a technical, tactical and strategic level.
  4. Actor Model Actors share nothing!

  5. Actor ▪ Isolated lightweight processes (Kotlin coroutines) ▪ Asynchronous and

    non-blocking ▪ Can only access internal state ▪ Share state by sending messages ▪ Each actor has an own mailbox (channel) ▪ Identified via address
  6. How an actor acts ▪ Processes one message at a

    time ▪ In response to a message, an actor can concurrently.. ¨ .. send a finite number of messages to other actors ¨ .. create a finite number of new actors ¨ .. designate the behaviour to be used for the next message it receives ▪ No sequence assumed in which the above actions are executed
  7. Message passing ▪ Messages are not necessarily delivered instantly ▪

    No sequence can be assumed on the receivers side ▪ .. but it can be assumed that a message is delivered ▪ Features unbound indeterminancy
  8. Advantages ▪ Easier to avoid ¨ Race Conditions ¨ Deadlocks

    ¨ Starvation ¨ Live Locks ▪ Variable Topology ▪ Easier to consider scalability and distribution
  9. And what about Kotlin?

  10. Channel Signature in Kotlin public interface Channel<E> : SendChannel<E>, ReceiveChannel<E>

    public interface SendChannel<in E> { public suspend fun send(element: E) public val onSend: SelectClause2<E, SendChannel<E>> public fun offer(element: E): Boolean } public interface ReceiveChannel<out E> { public suspend fun receive(): E public val onReceive: SelectClause1<E> public fun poll(): E? }
  11. Channel Buffer Types ▪ 4 different buffer types ¨ Rendevous

    (Unbuffered) ¨ Conflated ¨ Buffered ¨ Unlimited ▪ Backpressure through limited buffer
  12. Channel Example val channel = Channel<Int>() launch { for (x

    in 1..5) channel.send(x * x) channel.close() } for (y in channel) println(y) println("Done!")
  13. Actors in Kotlin ▪ kotlinx.coroutines provides an experimental API to

    build an Actor ▪ Returns a SendChannel public fun <MessageType> CoroutineScope.actor( context: CoroutineContext = EmptyCoroutineContext, capacity: Int = 0, start: CoroutineStart = CoroutineStart.DEFAULT, onCompletion: CompletionHandler? = null, block: suspend ActorScope<MessageType>.() -> Unit ): SendChannel<MessageType>
  14. Actors in action Goal: Build our own crypto currency ..

    implementing actor model .. but without the crypto stuff ▸ Simple bank account example
  15. Banking Account: Messages and State sealed class BankingMessage data class

    Deposit(val amount: Int) : BankingMessage() data class Withdrawal(val response: SendChannel<Boolean>, val amount: Int) : BankingMessage() data class Balance(val response: SendChannel<BankingAccountState>) : BankingMessage() data class BankingAccountState(val name: String, val balance: Int = 0)
  16. Banking Account: Actor @ObsoleteCoroutinesApi fun bankAccount(name: String) = GlobalScope.actor<BankingMessage> {

    var state = BankingAccountState(name) for (msg in channel) when (msg) { is Withdrawal -> if (msg.amount > state.balance) { msg.response.offer(false) } else { msg.response.offer(true) state = state.copy(balance = state.balance - msg.amount) } .. } }
  17. Banking Account: Access Methods typealias BankAccount = SendChannel<BankingMessage> suspend fun

    BankAccount.deposit(amount:Int) = this.send(Deposit(amount)) suspend fun BankAccount.withdrawal(amount:Int) = Channel<Boolean>() .also { this.send(Withdrawal(it, amount)) } suspend fun BankAccount.balance() = Channel<BankingAccountState>() .also { this.send(Balance(it)) }
  18. Banking Account: Access Methods typealias BankAccount = SendChannel<BankingMessage> suspend fun

    BankAccount.deposit(amount:Int) = this.send(Deposit(amount)) suspend fun BankAccount.withdrawal(amount:Int) = Channel<Boolean>(capacity=1) .also { this.send(Withdrawal(it, amount)) } suspend fun BankAccount.balance() = Channel<BankingAccountState>(capacity=1) .also { this.send(Balance(it)) }
  19. Banking Account: Usage val ron = bankAccount("Ron") val amelie =

    bankAccount("Amelie") ron.deposit(50) amelie.deposit(100) val success = ron.withdrawal(20).receive() println(ron.balance().receive())
  20. How to transfer money between two actors?

  21. Banking Account: Money Transfer @ObsoleteCoroutinesApi fun transfer(sender: BankAccount, receiver: BankAccount,

    amount: Int) = Channel<Boolean>().also { GlobalScope.actor<Unit> { val result = sender.withdrawal(amount).receive() if (result) { receiver.deposit(amount) } it.send(result) } } val success = transfer(sender = ron, receiver = amelie, amount = 50) .receive()
  22. Summary ▪ Shared state is evil in a concurrent environment

    ▪ Actor Model fits well in these situations ▪ Higher Memory consumption ▪ Errors are handled on a higher abstraction level
  23. Summary ▪ Actor Model is not well represented in Kotlin

    (kotlinx.coroutines) ▪ Backpressure in Kotlin can lead to known concurrency issues [..] we do not plan to turn kotlinx.coroutines into an actor-based framework [..]. In terms of kotlinx.coroutines actor is just a code organization pattern -- a coroutine plus an incomming channel. - Roman Elizarov, JetBrains
  24. Questions? Example can be found here https://gist.github.com/nernsting/ebf5e5c11cdd07ec797e00955ba6dac6