This article is just highlighting the tip of the iceberg from the original book. Please read the book for better detail and examples given by the author.
Concurrency is when the execution of the two pieces of code act as if they run at the same time.
- Breaking Temporal Coupling
Temporal Coupling happens when your code imposes a sequence of things that are not required to solve the problem at hand.
For example: Tick doesn't need to come before the tock if you want to stay flexible.
We need to allow for concurrency and to think about decoupling any time or order dependencies.
To find out what can happen at the same time, and what must happen in a strict order, we can use a notation such as the activity diagram.
The book gives a great example of the activity diagram by simulating the steps of preparing a Piña colada. Instead of running the step one by one, it can run some activities in parallel, like running the blender while preparing the glasses and the pink umbrellas.
When we're designing for concurrency, we're hoping to find activities that take time, but not time in our code.
Imagine two customers looking at the last pie on the display case and both are calling the server at the same time. One of them is going to be
This could happen when the process of reading the state and retrieving the value is asynchronous. The problem here is that neither process can guarantee that its view of that memory is consistent.
This is all because fetching and then updating the pie count is not an atomic operation: the underlying value can change in the middle.
To make it atomic, we can consider using semaphore. A semaphore is simply a thing that only one person can own at a time. When both processes run at the same time, one of them will proceed while the other will be suspended until the semaphore becomes available again.
Not only shared state, but problems can also pop up anywhere where your code shares mutable resources: files, databases, external services, and so on. Whenever two or more instances of your code can access some resource at the same time, you're looking at a potential problem.
Random Failures are often Concurrency Issues
Most languages have library support for some kind of exclusive access to shared resources. You could also argue that functional languages, with their tendency to make data immutable, make concurrency simpler. However, they still face the same challenges, because at some point they are forced to step into the real, mutable world.
Actors and processes offer interesting ways of implementing concurrency without the burden of synchronizing access to shared memory.
An actor is an independent virtual processor with its own local (and private) state.
A process is typically a more general-purpose virtual processor, often implemented by the operating system to facilitate concurrency.
There are a few things that you won't find in the definition of actors:
- There's no single thing that's in control
- The only state in the system is held in messages and in the local state of each actor
- All messages are one way, there's no concept of replying
- An actor processes each message to completion, and only processes one message at a time
Use Actors For Concurrency Without Shared State
IN the actor model, there's no need to write any code to handle concurrency, as there is no shared state.
Imagine a murder case, and detectives are investigating the case by combining all the facts, statements, and clues in a single blackboard. As the data accumulate, a detective might notice a connection and post that observation as well. This process keeps going on until the case is closed.
Some key features of the blackboard approach are:
- None of the detectives needs to know of the existence of any other detective
- The detectives may be trained in a different background, but they're all towards the same goal to solve the case
- Different detectives can come and go during the process
- There is no restriction on what may be placed on the blackboard
Computer-based blackboard systems were originally used in AI applications where the problems to be solved were late and complex, such as speech recognition, knowledge-based reasoning system, and so on.
Use Blackboards to Coordinate Workflow
The actor and/or blackboard and/or microservice approach to architecture removes a whole class of potential concurrency problems from your applications, but that benefit comes at a cost. These approaches are harder to reason about because a lot of the action is indirect.
You'll find it helpful to keep a central repository of message formats and/or APIs, particularly if the repository can generate the code and documentation for you. You'll also need good tooling to be able to trace messages and facts as they progress through the system.
Did you face a conflict state problem in your application before? Any bugs you traced are hard to reproduce or randomly occurred? Hopefully, the concept of Concurrency helps to address the issue better. Please leave your comment if you experienced one, I would be happy to learn as well!