The actor model in 10 minutes
Our CPUs are not getting any faster. What’s happening is that we now have
multiple cores on them. If we want to take advantage of all this hardware we
have available now, we need a way to run our code concurrently. Decades of
untraceable bugs and developers’ depression have shown that threads
are not
the way to go. But fear not, there are great alternatives out there and today I
want to show you one of them: The actor model.
The model
The actor model is a conceptual model to deal with concurrent computation. It
defines some general rules for how the system’s components should behave and
interact with each other. The most famous language that uses this model is
probably Erlang
. I’ll try to focus more on the model itself and not in how
it’s implemented in different languages or libraries.
Actors
An actor is the primitive unit of computation. It’s the thing that receives a message and does some kind of computation based on it.
The idea is very similar to what we have in object-oriented languages: An object receives a message (a method call) and does something depending on which message it receives (which method we are calling).
The main difference is that actors are completely isolated from each other and they will never share memory. It’s also worth noting that an actor can maintain a private state that can never be changed directly by another actor.
One ant is no ant
And one actor is no actor. They come in systems. In the actor model everything is an actor and they need to have addresses so one actor can send a message to another.
Actors have mailboxes
It’s important to understand that, although multiple actors can run at the same time, an actor will process a given message sequentially. This means that if you send 3 messages to the same actor, it will just execute one at a time. To have these 3 messages being executed concurrently, you need to create 3 actors and send one message each.
Messages are sent asynchronously to an actor, that needs to store them somewhere while it’s processing another message. The mailbox is the place where these messages are stored.
What actors do
When an actor receives a message, it can do one of these 3 things:
- Create more actors
- Send messages to other actors
- Designate what to do with the next message
The first two bullet points are pretty straightforward, but the last one is interesting.
I said before that an actor can maintain a private state. “Designating what to
do with the next message” basically means defining how this state will look like
for the next message it receives. Or, more clearly, it’s how actors mutate
state.
Let’s imagine we have an actor that behaves like a calculator and that its
initial state is simply the number 0
. When this actor receives the add(1)
message, instead of mutating its original state, it designates that for the next
message it receives, the state will be 1
.
Fault tolerance
Erlang
introduced the “let it crash” philosophy. The idea is that you
shouldn’t need to program defensively, trying to anticipate all the possible
problems that could happen and find a way to handle them, simply because there
is no way to think about every single failure point.
What Erlang
does is simply letting it crash, but make this critical code be
supervised by someone whose only responsibility is to know what to do when this
crash happens (like resetting this unit of code to a stable state), and what
makes it all possible is the actor model.
Every code run inside a process
(that is basically how Erlang
calls its
actors). This process
is completely isolated, meaning its state is not going
to influence any other process
. We have a supervisor, that is basically
another process
(everything is an actor, remember?), that will be notified
when the supervised process
crashes and then can do something about it.
This makes it possible to create systems that “self heal”, meaning that if an actor gets to an exceptional state and crashes, by whatever reason, a supervisor can do something about it to try to put it in a consistent state again (and there are multiple strategies to do that, the most common being just to restart the actor with its initial state).
Distribution
Another interesting aspect of the actor model is that it doesn’t matter if the actor that I’m sending a message to is running locally or in another node.
Think about it, if an actor is just this unit of code with a mailbox and an
internal state, and it just respond to messages, who cares in which machine it’s
actually running? As long as we can make the message get there we are fine.
This allows us to create systems that leverage multiple computers and helps us
to recover if one of them fail.
Next steps and other resources
This was a quick overview of the conceptual model that is the base of great
languages like Erlang
and Elixir
and libraries like Akka
(for the JVM
)
and Celluloid
(for Ruby
).
If I was successful in making you curious about how this model is implemented and used in the real world, this is the list of books that I read or am reading about this topic and can recommend:
And if you are interested in more details about the conceptual idea itself, I can’t recommend this video enough:
Interested in learning Kubernetes?
I just published a new book called Kubernetes in Practice, you can use the discount code blog to get 10% off.