What is race condition and how to avoid it?

The race condition is one of the most common problems that can occur in logic circuits, multithreaded or distributed software applications.

Today in this article we will discuss race conditions along with some examples, ways to detect them, and how you can avoid them.

What is the race condition?

Most of the software programs are multithreaded which means they can process multiple threads at the same time. The set of rules or instructions that is used to schedule the threads on a system is known as a thread scheduling algorithm.

The race condition is a condition of a software program that occurs when two or more threads can access a shared variable or data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don’t know the order in which the threads will attempt to access the shared variable or data.

Therefore, the result of the change in variable or data is dependent on the thread scheduling algorithm, i.e. both threads are “racing” to access/change the variable or data.

Thread-safe is the term used to describe a program or data structure free of race conditions when accessed by multiple threads.

Example of race condition

Suppose we have two threads 1 and 2 as shown below and a shared variable count. Thread 1 increases the shared variable count by 1 and thread 2 decreases count by 1.

Ideally, the following sequence of operations will take place.

In the above case, the final value is 0 as expected. However, if two threads run simultaneously without locking or synchronization the outcome could be wrong. The alternative sequence of operation below demonstrates this scenario.

In this case, the final result is -1 instead of the expected result 0. This happens because here increment and decrement operations are not mutually exclusive. Mutually exclusive operations are those that can not be interrupted while accessing some resources such as memory. So this shows the occurrence of race conditions.

How to avoid the race condition

There are two approaches in which you can fight race conditions.

  • Avoid shared state
  • Using synchronization and atomic operations

Avoiding shared state

The race condition arises because of shared variables or data so the best way to solve this problem is to eliminate the shared state.

Immutable objects whose state cannot be changed after creation are also thread-safe. So it is always recommended to use immutable objects as much as possible.

Using local thread variables is also thread-safe. It should be localized in such a way that each thread has its own private copy.

For check-then-act race conditions, you should use exception handling instead of checking. In this approach, the failure of an assumption to hold is detected at use time raising an exception.

Another way is using a concurrency model prohibiting shared states altogether, such as the actor-based concurrency model.

Using synchronization and atomic operations

Synchronization primitives such as the critical section can be used to ensure that a specific part of a program cannot be executed by more than one thread at the same time. A lock is a mechanism to enforce critical section behavior at the thread level.

Synchronization is one of the most powerful ways to prevent race conditions but using locks decreases the overall performance.

Atomic operations are those that finish their task as a whole that means these operations will either get fully completed or if failed it is rolled back to the initial state. For example withdrawal of money from ATM is an atomic operation.

Conclusion

Here we explained to you the race conditions in the context of multithreaded computer applications. Now if you have a query then write us in the comments below.

Leave a Comment