Concurrency in Software Application

Reeshabh Choudhary
7 min readDec 28, 2023

--

👷♂️ Software Architecture Series — Part 15.

“Concurrency is one of the trickiest aspects of software development. Whenever you have multiple processes or threads manipulating the same data, you run into concurrency problems.” — Martin Fowler & David Rice in the book “Patterns of Enterprise Application Architecture”

Concurrency is hard to test for. In general, automated tests are great foundational steps for software development but results of these tests are not sufficient enough to assure about security of the application in times of concurrency problems.

In day to day life of a software developer, transaction managers play an important role to avoid concurrency issues from bothering them. Transactions provide a framework, within which data manipulation is safe from different aspects of concurrency in an application. In database scope, Transactions pull together several requests that the client wants treated as if they were a single request.

However, in developmental process, scope of work is not limited to a single database transaction but there can be situations where data transcends transaction (offline concurrency). We also encounter multi-threaded application servers where concurrency issues can be faced and usually are dealt with use of several platforms or framework.

Concurrency Problems

Every concurrency control system tried to prevent the following two fundamental problems:

· Lost updates — when two entities are working on same object and update of one’s work is lost or overwritten by the other.

· Inconsistent read — reading two things which are correct pieces of information however not correct at the same time.

Effect of the above concurrency problems can be seen on the correctness and liveness of the application. If we take measures for correctness, such as limiting access so that only one entity can work on an object at a time, it might affect liveness of the system, which would be the ability to do multiple things at a time.

Common Solutions for Concurrency: Isolation & Immutability

Isolation

Isolation is partitioning the data to ensure any chunk of it can be accessed by one active agent. It is a vital technique which greatly reduces chances of error. It helps program enter an isolated zone where it does not have to worry about concurrency problems. For example: file locks.

Immutability

Immutability means data can not be modified. Although, this can not be the case all the time, yet, by identifying some data as immutable, we can relax our concurrency concerns.

How to deal with Mutable date?

As we know, while working in enterprise application, majority of data cannot be immutable hence, we will discuss concurrency control measures to deal such scenarios.

Optimistic Locking (Conflict Detection)

If the conflicts are rare and consequences of them are less sever, optimistic locking can be a favored approach. It allows two entities to operate on an object by making a copy of it. First entity to commit the changes usually goes trouble free, however, second entity while committing changes will face conflict and will have to deal with it by modifying changes accordingly. Basically, it uses a version marker or timestamp mechanism to detect conflicts at the time of updating the data. This marker could be a timestamp, a sequential counter, or any unique identifier associated with the data that changes whenever the data is modified.

When a process/thread intends to modify the data, it first reads the current version marker. Upon attempting an update, it checks whether the version marker has changed since the data was initially read. If the version marker remains unchanged (indicating no other process has modified the data in the meantime), the system allows the update. The process/thread increments or changes the version marker to signify the data modification. However, if the version marker has changed (indicating a modification by another process/thread), the system detects a conflict. At this point, the process can handle the conflict in several ways: retry the operation, notify the user, merge changes, or perform conflict resolution based on specific application logic.

This approach enhances system performance by increasing concurrency however handling conflicts requires careful consideration and custom logic. Automatic conflict resolution might not always be straightforward, leading to complexities in managing data consistency and often manual intervention might be required.

Pessimistic Locking (Conflict Prevention)

In this case, first entity to work on the object restricts other entity to work upon it until changes of first entity is committed. The idea is to prevent the conflict from happening in first place. This approach involves using locks to control access to shared resources like databases or critical sections of code. The goal is to prevent conflicts between different processes or threads that may try to access or modify the same data simultaneously, which could lead to inconsistencies or errors.

An assumption is made beforehand that the conflict will occur and hence a cautious approach is taken by locking resources whenever they are accessed, allowing only one process/thread to have exclusive access to the resource at a time. This helps maintain data integrity but can potentially impact performance because it restricts concurrent access.

The two common types of locks used in pessimistic locking are:

  1. Read (Shared) Lock: Multiple processes/threads can acquire a read lock simultaneously, allowing them to read the data. However, this lock prevents any process/thread from obtaining a write lock while the read lock is held. This ensures that no changes are made to the data while it’s being read.
  2. Write (Exclusive) Lock: This lock grants exclusive access to a single process/thread, allowing it to modify the data. Once a write lock is acquired, no other locks (read or write) can be obtained until the write lock is released. This prevents concurrent modifications that could lead to inconsistencies.

Pessimistic approach might seem more effective while reducing concurrency concerns, but it affects system’s liveness to great effect. Hence, use of these solutions must be entirely situational usually based upon frequency and severity of conflicts.

Pessimistic approach often leads to deadlocks. Deadlocks usually occur when two entities working on two objects separately try to make changes on the object which other was working upon. It leads to a scenario where each entity is waiting for the lock to get over to progress. Problem gets worse when number of entities increases.

There can be different approaches to deal with deadlocks. One approach usually picks a victim after detecting a deadlock and discards the changes related to it so that others can make progress. Ensuring time limits on locks can also be a viable option but will affect liveness of system especially in the cases when deadlocks are not present, yet system makes entities wait to work on the respective objects.

Transactions

Transaction is a bounded sequence of work where all participants are in consistent state before and after a transactional process. A Transaction must complete on all or nothing basis. A software transaction must adhere to ACID properties, coined in 1983 by Theo Härder and Andreas Reuter.

ACID Properties

Atomicity

Atomicity ensures that while working in the scope of transaction, each step of the sequential action set must either successfully commit or else rollback. The ability to abort a transaction on error and have all writes from that transaction discarded is the defining feature of ACID atomicity.

Consistency

Ensures that state of resources must be in consistent state before and after a transaction. Consistency depends on the application’s notion of invariants, and it’s the application’s responsibility to define its transactions correctly so that they preserve consistency. Atomicity, isolation, and durability are properties of the database, whereas consistency is application oriented.

Isolation

Ensures concurrently executing transactions are isolated from each other which implies that result of an individual transaction must not be visible to any other open transactions until that transaction commits successfully. Serializability on database can be used to achieve isolation but it leads to bad performance.

Durability

Ensures that result of a successful transaction is committed and made permanent, even if there is a hardware fault or the database crashes.

Transactions and concerns

In software world, we usually run into transactions in terms of databases however, there can be other scenarios where transactions can come handy.

Note: In general, a transactional process is usually kept short to increase liveness of the application as well as increase throughput. A common approach is to open a transaction as late as possible ensuring reads are done outside transactional scope and transaction is opened only for an update. However, leaving reads outside transactional scope might also lead to situation of inconsistent reads.

Most transactional systems use the SQL standard which defines four levels of isolation with Serialization being the strongest one.

Serializable

If the isolation level is serializable, system makes sure that in scenarios where two entities are working on an object where one is modifying it and other is reading it, then, the one reading will always get the correct state which would be either the state before modification or the state after the modification.

Serialization ensures system correctness but takes a good impact on liveness of the system.

Summary

In this article, we have discussed about concurrency and issues related to it. We also looked at some common techniques which can be used to avoid or solve concurrency problems. Each solution comes with its pros and cons and it is up to application developer to make sure the solution being implemented fits the situational requirement of the system.

--

--

Reeshabh Choudhary
Reeshabh Choudhary

Written by Reeshabh Choudhary

Software Architect and Developer | Author : Objects, Data & AI.

No responses yet