Demystifying concurrency using Actors, Let there be Abstraction (Part 1)
Hi there, you probably got this silly joke about actors :) We will talk about Actor Model which has nothing to do with the cinematography. Despite the fact that the topic itself is not one of the easiest ones conceptually, I’ll do my best to share as much as I know about it. Feel free to reach out (https://www.linkedin.com/in/archil-sharashenidze/) and discuss any interesting questions you have while reading this blog. And now, without farther ado let’s start our discussion around actors and particularly how they make concurrent computations much smoother and straightforward.
Why Actor Model?
Actor Model is fully asynchronous, distributed by design, model of concurrent computation. Technologies based on actor model, serve millions of users every day, all around the world!
This section is no doubt the most important in the whole blog. Without understanding the need and greatness of Actor Model you will probably ignore it and just close the tab. So let me begin with introducing how widely used and high performant is actor model. Particularly one of its most famous implementations Akka that will turn your JVM experience upside-down.
Actor model was first mentioned in 1973 by Carl Hewitt in one of his scientific papers. According to original definition actor model is a fully asynchronous concurrency model, which is distributed by default and has enormous ability to scale.
The uniqueness and power of Actor Model was not left without attention and in mid 80s one of the biggest telecommunications company Ericsson created their own language called Erlang with built in Actor Model implementation.
Erlang is used in telecommunication, internet and other networking infrastructures by Ericsson, T-Mobile and other telecommunication giants. Erlang is the reason why all the calls and network connections remain uninterrupted during extremely high load and re-deployments.
Success of Erlang triggered other implementations to see sunlight. One of the most famous is Akka, which is originally written in Scala and provides support for Scala as well as for Java. Akka completely re-defines the way you write code on JVM.
Proven in production
This is just a small list of companies using Akka, for further usage information you can visit https://www.lightbend.com/case-studies.
Throughout our conversation we will follow top-down approach. We will start talking about Actor Model in mostly conceptual level. After understanding the whole idea and abstraction from A-Z, we will switch to more practical staff and cover the Akka framework with multiple comprehensive examples(This will be done in separate blog, you can find its link at the end). For the coding language I chose Scala, as Actor Model is more close to Functional Programming (Erlang is a functional language btw). Don’t worry I’ll explain every strange aspect regarding Scala syntax on the fly. As mentioned above, Akka provides full functionality in Java as well, therefore you can enjoy its power in the language of your choice. Blog might seem lil bit long, but I strongly recommend to follow it smoothly to get maximum from it.
At the end of the day, it’s all about fundamentals and understanding how things work.
What is an Actor?
Here we go, Actor is a fundamental unit of computation (just like objects in OOP) that has to embody 3 things:
Processing: The business logic kinda thing. Just like method implementing some logic.
Storage: The statefulness, just like traditional objects having internal state.
Communication: The ability of an actor to communication with other actors. Actors communicate via messages.
The main idea around actors is that they are independent parts of the system, and communication between them happens fully asynchronously. It means when actor A sends a message to actor B, A does not have to block and wait for the answer from B.
Let’s deliver another definition:
Actor model is a non-blocking, asynchronous, reactive model of concurrency. (I know this 3 words mean almost same) :)
Let’s look at the example, Actor A sends message to Actor B. And then in contrast after receiving message from A, B responds. 2 things that you might have noticed are the abilities to send and receive messages somehow. The great thing about actor model is thats it’s all about abstraction. Actor model does not really care about the low level implementation staff. Therefore we will follow this very convention and go through some code later in this blog. (This is called top-down approach)
Actor can send messages to actors it knows. (again, later on the implementation)
Now let’s have a look at actor family example. As you can see the situation is that Alice informed Bob about her pregnancy, and Bob felt very excited about it. As 9 months have passed, Alice gave birth to Abbie. They also exchanged couple of “words”. As you can guess all 3 family members are actors.
Actors can create another actors.
In the third example we will consider a queue management system in the bank. As you can see we have 4 actors here. Alice, Bob and John get their queue numbers as illustrated. What happens on the Queue Management side? Imagine, Queue Management actor represents an ordinary machine that provides queue numbers. After every message it receives, it simply instructs itself to provide incremented number for next message. Don’t look at it as just internal counter state change. The main idea here, is that Queue Management can designate how next received messages should be handled. (This can be done very nicely using a pinch of FP). We call this handling the behaviour of an actor. It’s not a must to update the behaviour for the next messages, actor can choose to have same behaviour for any new message.
Actors should designate how it should handle the next messages it receives. (Not updating and keeping the same behaviour is also a designation.
Hope you got the point from above examples. These are the 3 axioms of Actor Model.
When an Actor receives a message it can:
- Create new Actors
- Send messages to Actors it knows
- Designate how it should handle the next message it receives
Congratulations, we now completed the upper-most abstraction layer of our blog. It’s time to get 1 step down and discuss some more conceptual details.
How do Actors find each other?
Actors have addresses. That’s it. If an actor knows the address of another actor it can send message to it. Please don’t try to think of the implementation for now. As simple as it is. Just like in real life, if you know my address you can send whatever you want to me. The main thing here is, that we may or may not be close to each other. We can leave in the same house or we can be separated by thousands of miles. Just like actors leaving in the same machine (localhost or same address space) or actors that are running on different machines far away from each other. As long as address is known, sending message is only matter of implementation. Remember we are still on the abstraction layer and we are not supposed to talk about implementations. You can implement Actor Model and handle communication between local and remote actors as you wish. As we are talking abstract, this example will work for us.
Actors can be located in the same address space as well as in separate ones.
A core pillar of OOP is Encapsulation. It simply dictates, that objects internal state should not be accessed directly from outside. The object is responsible to expose safe ways to access internal state to protect the invariant nature of its encapsulated data. In a single-threaded universe everything is simple, we just provide some methods to access internal state and that’s it. But hopefully there is no single-threaded universe around us. Objects’ methods can be accessed by thousands of threads simultaneously, hence breaking the encapsulation and collapsing internal state.
And Boom! To the rescue for our crashed de-capsulated data comes …
This entrance would have been pretty charming about 20 years ago. But now when application level concurrency with green threads have been battle tested in various challenging scenarios (have a look at concurrency mechanism in Go) it’s so annoying to think about concurrency on JVM in traditional way. Heavyweight OS level threads and synchronisation implementations like locks eventually lead to no-win situation. Missing synchronisation may lead to corrupted data, over-locking increases chances of deadlocks. Any way, Locks and Threads in general are too heavy on JVM. Furthermore, locks only really work well on local machines. When it comes to coordinating across multiple machines, performance of distributed locks sucks…
How Actor Model gets rid of Locks?
As messaging is a key that unlocks mystery of communication between actors, what can such an outstanding software engineer like you think of it? “Message” seems like it needs another word to complete logical circle and do some magic. As you might have guessed, “Queue” is the answer. Every Actor is armed with a special queue called Mailbox. When actor receives messages, they are thrown in the mailbox of the recipient actor. Messages are taken (“dequeued”) from mailbox one by one, hence processed sequentially. Are you getting it? As simple as it sounds. No synchronisation is needed here as only 1 message is processed at a time. Let’s quickly have a look at the famous Bank Account example. We have a bank account with balance 100. Imagine 2 concurrent users try to withdraw money. Alice wants 80 and Bob 30. While processing these 2 requests, we would take 3 simple steps:
- Read current balance.
- Check if current balance is enough to withdraw requested amount.
- Return Success or Failure depending on step 2.
Step 2 in this application is called a “critical section”. During concurrent requests it can happen that both Alice and Bob pass step 2 checking part together. It will lead to successful withdrawal for both users and total balance will become negative: -10. Usually we surround critical sections with locks, so that negative balance never occurs. But now let’s have a look how actor model without any locks implements this bank example.
Alice’s and Bob’s messages about withdrawal are thrown in Bank’s Mailbox (Queue). After it, the messages are taken from the mailbox 1-by-1 and processed sequentially. This sequential nature of message processing removes any need of synchronisation. The order of withdrawal in this case only depends on who made it first to the mailbox. If Alice’s message arrives first she will get the money, otherwise Bob will get what he wants.
This is how actors get rid of locks!
What we have learned so far is that Actor Model is a pure abstraction that provides ability to create highly distributed concurrent applications using things called Actors that communicate with each other asynchronously via messages. I think you should have lot’s of unanswered questions regarding the abstraction as well as the implementation itself.
Don’t worry, I just decided to split abstraction and implementation in 2 parts. In order to dive deep into more interesting ideas around Actor Model and see how actually it is implemented in Akka, checkout the second part of this blog series Let there be Implementation on the link below: Demystifying concurrency using Actors, Let there be Implementation (Part 2)
In the above blog we will cover all the basics we need to actually code Actors using Akka. We will go through different code examples, and also talk about how Akka Runtime uses “internal threads” to manage concurrency. Without using heavyweight OS threads that we are used to on JVM.
Hope you enjoyed:)
Let’s connect: https://www.linkedin.com/in/archil-sharashenidze/
All materials used to understand actor model and deliver this blog to you, are collected by me in the process of learning. Checkout my github repo containing different resources about actor model.