The Pragmatic Programmer EP.7 — Concurrency

Natnicha R.
CodeX
Published in
6 min readMay 30, 2022

Let’s talk about the meaning of concurrency and parallelism first.

Concurrency is when the execution of two or more pieces of code act as if they run at the same time. Parallelism is when they do run at the same time.

To have concurrency, you need to run code in an environment that can switch execution between different parts of your code when it is running. This is often implemented using things such as fibers, threads, and processes.

To have parallelism, you need hardware that can do two things at once. This might be multiple cores in a CPU, multiple CPUs in a computer, or multiple computers connected together. — The Pragmatic Programmer

Transforming to a concurrent process

A common cause of concurrency problems is designing in sequential; do this thing and then always do that thing. To reduce your application execution time, instead, allow your application concurrency by keeping your mind that your code is decoupling — no dependency. Then, review and analyze your activity diagram whether what we can execute at the same time.

An activity diagram consists of a set of actions drawn as rounded boxes. The arrow leaving an action leads to either another action (which can start once the first action completes) or to a thick line called a synchronization bar. Once all the actions leading into a synchronization bar are complete, you can then proceed along any arrows leaving the bar. — The Pragmatic Programmer

Thanks to wikipedia

After analyzing, we will see the opportunities for concurrency to make your application productively. Then, parallelism is also come in here. Remember that parallelism is about hardware concerns. If we have multiple processors, then we can split tasks which we can reduce the overall time spent.

Semaphores

In the case that you go to a department store for a new shoes. You order a special edition of a sneaker with poplar size — 8, and a salesman acknowledges that it’s available in a stock, while another side of the shop, a customer orders the same shoes’ specification. Unfortunately, at that time, there is only 1 pair of shoes available. The result is that only one customer can get this shoes. The other may very unsatisfied.

The problem is the shared state. Each salesman checks the availability of the same product without regard for the other. When the number of available shoes is changed, the displayed number at point of sale to one of the salesman is now out of date.

The problem here is not that two processes can write to the same memory. The problem is that neither process can guarantee that its view of that memory is consistent. — The Pragmatic Programmer

To make it effective, let’s apply a semaphore to our application.

A semaphore is a simple concept allowing only one person can own a particular thing at a time. So, create it and use to control the resources.

Applying to the shoes shop, we should create a semaphore to control access to shoes information and update number of shoes availabilities. Anyone who want to do that, just hold the semaphore. It commonly is named as lock/unlock or claim/release, for example,

It’s not a good idea to delegates responsibility for protecting access to the shoes to the people who use it. Let’s centralize the control like this:

Let see where semaphore is locked and unlocked, don’t forget to unlock the semaphore after locking whenever you are able to get the shoes or not. As you can see the codes, I put the semaphore unlocking under ensure statement (Line:13).

Multiple resources handling

In the case that a salesman should serve you a pair of shoes wrapped by bag. So, a process should be taking the shoes and allocating a bag to put the shoes in. Our code will be:

Some language may provide a protect feature to handle this problem like this:

Above codes could work, but, many times, when a pair of shoes is available but bag it not. This let the salesman give the shoes back to the stock. The pragmatic solution is that we should create a new module to handle both shoes and bag, not a responsibility of shoes or a responsibility of bag component. This new module will tell the salesman whether getting it success or fail. Moreover, if you want to serve every product with bag, you probably create a generic component to get item a product with a bag.

Concurrent updates

When more than 1 instance, try to access the same resources at the same time, it is high risk to have problems. For example, while process-1 stores values in the application, process-2 is trigged and replaces them with new data. This leads to a tip to investigate the symptom of the application that

Random Failures Are Often Concurrency Issues — The Pragmatic Programmer

Actors and Processes

Let see definitions of an actor and a process from the book.

An actor is an independent virtual processor with its own local (and private) state. Each actor has a mailbox. When a message appears in the mailbox and the actor is idle, it kicks into life and processes the message. When it finishes processing, it processes another message in the mailbox, or, if the mailbox is empty, it goes back to sleep.

A process is typically a more general-purpose virtual processor, often implemented by the operating system to facilitate concurrency. Processes can be constrained (by convention) to behave like actors — The Pragmatic Programmer

The authors recommend to let actors for concurrency with nothing to share. When there are enough processors to run concurrency, you should run actors on it because codes in each actor would be the same and you are no need to add additional codes to handle concurrency.

Here is the overall flow:

  • A customer wants a new pair of shoes.
  • A customer asks a salesman for a pair of shoes by a given specification.
  • The salesman checks balance in a stock.
  • If a pair of shoes is available, the system will give it to the customer, and inform the salesman to add it to the bill.
  • If a pair of shoes is not available, the system will tell the salesman to let salesman apologizes to the customer.

If we implement the previous example — the shoe store — and start the service. Allowing for concurrency, we would then see the following conversation.

Blackboards

Photo by cottonbro

The authors mentioned a blackboards approach to investigate concurrency problems like when detectives are trying to solve a case where the blackboards features are:

  • The detectives do not need to know what each other does, just add information (s)he gets into the blackboard.
  • Based knowledge of each detective member may be different. They may be trained by different disciplines.
  • Team members may not work in the same location, or even they are not going through the same process.
  • There are no restrictions on what can be put on the blackboard. It can be a photo, soft files, sentences, and so on.

This approach allows team members to reach the same target while they are in different states and locations. Whereas some people are busy with the gathering process, others are seriously connecting in-hand data. Eventually, they are focusing on the conclusion of the case.

Turning on to the action in pragmatic programmers, we use a blackboard approach to investigate problems when we write applications. So, our blackboard turns to these features:

  • Problem evidence (responses) can arrive in any sequence.
  • All information will be gathered by different people from different locations and different time zones.
  • Some data may be taken by automatic systems.
  • Any new information may lead to new problems and need additional supporting information.

Note that the order of data retrieval is not significant. Moreover, feedback can be revealed on your application and it can lead to new applicable rules.

Messaging Systems Can Be Like Blackboards — The Pragmatic Programmer

Messages between services and event logs can be put on our blackboard. Both of them can be collected by systems or platforms, for example, Kafka. These can eliminate the potential for application concurrency problems. In practice, it comes with project expenses. So, try to centralize the resources to handle them and create a unique trace-id to track the process in different APIs or systems.

Please note that all examples shown above are samples to only emphasize understanding the concept. It may be simplified to comprehend, not correctly by the language syntax.

Thanks to The Pragmatic Programming book for the above knowledge, if you are interested in reading this book, click here for more detail.

--

--

Natnicha R.
CodeX
Writer for

Software Engineer, Backend Designer, Algorithm Developer