Note: The examples below assume the program in question was written in C++.
A race condition can occur in a multithreaded program when you do not use semaphores. A race condition is a situation where two threads are "racing" towards use of the same variable or some other data structure. For example, suppose Thread 1 contains the statement i=foo(i); and Thread 2 contains the statement a=b=i;. If you do not use a semaphore to block one thread, timeslicing could, in theory, result in either of the following sequences of events, among others:
Sequence 1: Thread 2: Load value of i into register Thread 1: Load value of i into register Thread 2: Store value of i in register to b Thread 1: Call foo(i) using value of i in register Thread 2: Store value of i in register to a Thread 1: Store result of foo(i) to i Sequence 2: Thread 1: Load value of i into register Thread 1: Call foo(i) using value of i in register Thread 1: Store result of foo(i) to i Thread 2: Load value of i into register Thread 2: Store value of i in register to b Thread 2: Store value of i in register to a
If i is an integer with a value of 3 before the sequences begin, and foo(x) is an integer function that returns 3*x+1, the variables at the end of sequence 1 will be: a=1, b=1, i=10; the variables at the end of sequence 2 will be: a=10, b=10, i=10.
A race condition can occur even when you do use semaphores. In such a case, it may be an indication of incorrect program logic. Suppose, for example, that two threads both assign a value to a variable, and that a third thread reads the value of that variable:
Thread 1: Thread 2: Thread 3: i=3; i=4; j=i;
Even if you request a semaphore before each assignment to i in threads 1 and 2, and release the semaphore after the assignment, there is no way of predicting whether j will be assigned the value 3 or 4 (or even the value of i before Threads 1 and 2 assigned to it). In this example, the race condition is simply poor programming logic.
You may want to use a race condition to determine which of two or more threads completed a given task first. For example, if the statement in Thread 3 was:
if (i==3) cout << "Thread 1 completed first" << endl; else cout << "Thread 2 completed first" << endl;
and you had protected each assignment to i with a semaphore, the statement in Thread 3 would be reliable.
Remember to use semaphores not only on pointers to objects, but on the objects themselves. If two pointers point to the same object and you only use semaphores to lock the pointers, two different threads using different pointers can access the same object simultaneously.
You can use a Storage change breakpoint to find race conditions such as those shown above. By placing a Storage change breakpoint on the address of a variable, you can find all statements that change the variable and make note of the order in which different threads change it.
Race conditions are another example of a timing problem that may only occur when you are debugging your program, because the debugger may affect the order in which threads are accessing shared data.