We have CPUs and they are getting better every day, in helping us process fast. But CPUs are not getting faster!!
What’s happening is that we now have multiple cores on them. And, to take advantage of this, we need a way to run our code concurrently.
Some concurrency models that we are already familiar with, are:
For concurrency, we as developers have used Threads. But Decades of untraceable bugs and developers’ ache have shown that threads are not the way to go. Anyone who has done multi-threading in the past won’t deny how hard and painful it is to manage multi-threaded applications.
But fear not, for developers who have experienced the problems with creating and managing multi-threaded applications and are looking for a higher level of abstraction to get away from threads and locks, there are great alternatives out there and one of them is : The Actor Model.
What is Actor Model?
Actor Model provides a level of abstraction that makes it easier to write correct concurrent, parallel and distributed systems.
It is a model of concurrency in which the universal primitive is an actor.
Wow, wait a sec, we got three heavy terms:
- Universal Primitive
So we need to understand these terms before going deep into Actor-Model.
- Concurrency: We have been doing concurrent programming using threads for a long time. Two threads (or processes) executing concurrently on the same core through context switching is concurrency.
- Universal Primitive: Well let’s see, we know threading model, so there the multiple threads are primitives. Similar to threading models, if we have multiple threads we have concurrency and if we have multiple actors we are achieving concurrency. So Actors are the actual primitives in Actor-Model.
- Actors: Actors are abstractions over threads and processes. An Actor is the fundamental body of computation. And to be a that, it has to comprise three things:
Now Let’s have a deeper understanding of the Actors:
Properties of Actors:
- Actors are persistent:
When we give some execution to a Future it performs it and just exits and no longer exists. But an actor, whether it is doing something or not but it stills exists. And it exists until we tell it to stop existing or if there’s some failure or exception.
- Encapsulate internal state: Threads don’t have state and Futures don’t have a state, But actors have a state to be maintained.
- Actors are asynchronous
Now we have Actors, but as Carl Hewitt says, One Actor is No Actor, they come in systems. And to be a part of the system, they need to communicate. And to communicate, Actors send and receive messages. Actors don’t use channels or any intermediates for communications.
Actors are identified by an address that contains location and transport information.
One address may represent many actors and one actor may have many addresses. Addresses of Actors do not change during its failure or restarts. So these addresses are used for communication.
Now as we can think, there could be multiple messages for one Actor. But An actor can process one message at a time. So, if there are multiple messages, they are gonna go to the mailbox queue and get processed one at a time. And these Mailboxes are persisted outside the actor instances so that messages are not affected by failure or restart of an actor.
OK now the question arises: What if an actor fails with exception and has to be restarted. Who’s gonna take this responsibility? And answer is,
Supervisor Actor: The running state of an actor is monitored and managed by another actor called supervisor. It constantly monitors running state of actor and can perform actions based on state of the actor (e.g. unhandled error => restart the actor).
Now we know that Actor Model comprises, Actors, Messages, Mailboxes, Supervisor Actors. So in a gist, let’s have a quick look at some basic queries regarding Actors:
What can actors do?
- Create new actors.
- Receive messages and in response, they can make local decisions (e.g. alter local state), perform arbitrary, side-effecting action (e.g. writing to a log file).
- send messages to other actors or reply back to the sender. It can respond to the sender 0 or more times.
Note: A Message will be delivered at most once.
When an actor receives a message, What can be done by that actor?
- It can create some more actors
- It can send messages to actors whose address it had before.
- It can designate what its gonna do with next message it receives.
When to use actors ?
- Processing pipeline
- Streaming data
- Multi-user concurrency (eg: Shared Accounts, Gambling sites)
- Applications with shared state
When not to use?
- Performance critical applications
- Non-concurrent communication involved and there is no mutable state
How to prevent deadlock while working with Actors?
Wrap the message in Future/Completable Future. Also, when an actor has to send a message to itself, it can be sent in Future so that we can prevent deadlock in that scenario too.
Well, every technology, methodology, framework, tool or language has some pros and cons. And Actor model is following the same path. There are some small hurdles that we would face while working with Actor Model:
- Too many actors, handle all of them
- Testing, while maintaining concurrency.
- Debugging, because of state with concurrency.
And here are few Frameworks/Languages using the Actor Model
- Erlang / Elixer
- CAF (C++)
- Akka (JVM)
- Pulsar (Python)
- Orleans (.Net)
- Celluloid (Ruby)
For our next blog, we will be picking up with Akka and understand how Akka with Java will be helpful for maintaining concurrency in an abstracted and easy way.
Thanks for the read.
Hope this blog would be helpful to you for understanding the basics of the Actor Model. For more doubts and examples regarding various technologies, framework and tools, feel free to go through our blogs, because we at Knoldus believe in gaining knowledge and growing our skills together.