Search⌘ K
AI Features

A Race Condition Example

Discover the impact of race conditions on program correctness when multiple threads share mutable data. Understand how thread scheduling can cause unexpected results and why careful synchronization is necessary to avoid corrupted shared variables in D programming.

We'll cover the following...

The correctness of the program requires extra attention when mutable data is shared between threads.

Example

To see an example of a race condition, let’s consider multiple threads sharing the same mutable variable. The threads in the following program receive the addresses as two variables and swap their values a large number of times:

D
import std.stdio;
import std.concurrency;
import core.thread;
void swapper(shared(int) * first, shared(int) * second) {
foreach (i; 0 .. 10_000) {
int temp = *second;
*second = *first;
*first = temp;
}
}
void main() {
shared(int) i = 1;
shared(int) j = 2;
writefln("before: %s and %s", i, j);
foreach (id; 0 .. 10) {
spawn(&swapper, &i, &j);
}
// Wait for all threads to finish their tasks
thread_joinAll();
writefln("after : %s and %s", i, j);
}

Although the program above gets compiled successfully, in most cases it would work incorrectly. Observe that it starts ten threads that all access the same two variables i and j. As a result of the race conditions that they are in, they inadvertently spoil the operations of other threads.

Also, observe that the total number of swaps is 10 times 10 thousand. Since that amount is an even number, it is natural to expect that the variables end up having values 1 and 2, their initial values:

before: 1 and 2
after : 1 and 2    ← expected result

Although it is possible that the program can indeed produce that result, most of the time the actual outcome would be one of the following:

before: 1 and 2
after : 1 and 1    ← incorrect result
before: 1 and 2
after : 2 and 2    ← incorrect result

It is possible, but highly unlikely, that the result may even end up being “2 and 1”.

The reason why the program works incorrectly can be explained by the following scenario between just two threads that are in a race condition. As the operating system pauses and restarts the threads at indeterminate times, the order of execution of the operations of the two threads, explained below, is likely.

Let’s consider the state where i is 1 and j is 2. Although the two threads execute the same swapper() function, remember that the local variable temp is separate for each thread, and it is independent of the other temp variables of other threads. To identify those separate variables, they are renamed as tempA and tempB below.

The chart below demonstrates how the three-line code inside the for loop may be executed by each thread over time, from top to bottom, operation 1 being the first operation, and operation 6 being the last operation. Whether i or j is modified at each step is indicated by highlighting that variable:

As you can see, at the end of the previous scenario both i and j end up having the value 1. It is not possible that they can have any other value after that point.

The scenario above is just one example to sufficiently explain the incorrect results of the program. Obviously, the race conditions would be much more complicated in the case of the ten threads.