N:=N+1 at about the same time
is called a race condition since one of the updates
may get lost.
Two SR processes doing sum:=fn(sum,i) at about the same time
is also a race condition: sum takes the place of N,
i takes the place of 1,
and the function fn takes the place of +.
SR Program: A Race Condition.
There is a fixed $10 million in this bank (10,000 accounts that each start
out with $1000) but the auditor sometimes sees more, sometimes less.
Can you figure out what is going wrong here?
SR Program: A Different Kind of Race Condition.
An another example of what can go wrong, this time using a linked list.
Suppose we have a computer, single CPU with round-robin context switching or multiple CPUs, shared memory.
memory:
----------------------------------------
| process A . shared queue . process B |
----------------------------------------
shared queue:
head -> 1 -> 2 -> 3 -> 4 ->null
^
tail ------------------|
to add a new node x to the queue
old_tail is a local variable
link(node x) := null /* null out x's next pointer */
old_tail := tail /* follow tail to node 4 */
tail := address x /* change tail from 4 to x */
link(old_tail) := address x /* change 4 from ->null to ->x */
Suppose processes A and B that share the queue both try to add new nodes 5 and 6 respectively to the queue at about the same time.
A Race Condition Adding to a Queue.

PostScript version of above.
If you cannot spawn an external PostScript viewer or see GIFs,
try this.
We need a way to keep the instructions from A and B that add an element to the queue from getting interleaved.
process A process B
--------- ---------
... ...
add_to_queue(queue, node 5) add_to_queue(queue, 6)
... ...
We will call the instructions in each process that add to the queue a critical section of code.
Earlier queue example is an example of a race condition:
Another very simple example:
suppose processes A and B execute N:=N+1
at about the same time.
Process A Process B --------- --------- shared variable N initially 0 shared variable N ... ... N := N + 1 N := N + 1 ... ...
On load/store register architectures,
N:=N+1 compiles to something like
load N into accumulator
accumulator := accumulator + 1
store accumulator into N
On a shared-memory multiprocessor or a uniprocessor with context switching, the machine language instructions could get interleaved thusly
process A: load N to acc (context switch)
B: load N to acc (context switch)
A: acc := acc + 1 (context switch)
B: acc := acc + 1 (context switch)
A: store acc to N (context switch)
B: store acc to N (context switch)
Final value of N is 1 and is incorrect.
Any interleaving (other than all A followed by all B or vice versa)
will give incorrect results.
These examples show that concurrently executing processes that share data need to
Critical sections need to be done as if they were one atomic action to avoid race conditions.
The mutual exclusion problem is
process Pi, i = 1, 2, 3, ...
while (true) {
...
non critical section code;
...
mutex_begin(i); /* pre-protocol */
...
critical section code;
...
mutex_end(i); /* post-protocol */
}
The mutual exclusion problem becomes, how do we implement mutex_begin() and mutex_end()?
Several approaches:
Hardware solution
mutex_begin() { mutex_end() {
disable interrupts; enable interrupts;
} }
or
mutex_begin() { mutex_end() {
lock bus; unlock bus;
} }
Software with busy waiting possible solution
shared boolean lock_flag = false;
mutex_begin() { mutex_end() {
while (lock_flag) { lock_flag = false;
/* skip */ ; }
}
lock_flag = true;
}
Another software with busy waiting possible solution
shared int turn = 1;
mutex_begin(i) { mutex_end(i) {
while (turn != i) /* skip */; turn = (i % 2) + 1;
} }
Another software with busy waiting possible solution
shared boolean flag[2]; /* flag[i] is intention of Pi to enter CS */
j = (i%2)+1; /* i=me, j=other */
mutex_begin(i) { mutex_end(i) {
flag[i] = true; flag[i] = false;
while (flag[j]) /* skip */; }
}
Another software with busy waiting possible solution
mutex_begin(i) { mutex_end(i) {
while (flag[j]) /* skip */; flag[i] = false;
flag[i] = true; }
}
Software with busy waiting solution
shared int turn = 1;
shared boolean flag[2];
mutex_begin(i) { mutex_end(i) {
flag[i] = true; flag[i] = false;
turn = j; }
while (flag[j] and turn=j) /* skip */;
}
Hardware solution with busy waiting
shared boolean lock_flag = false;
mutex_begin() { mutex_end() {
do { lock_flag = false;
test_and_set(R, lock_flag, true); }
} while (R = true);
}
Busy waiting, either hardware or software, wastes CPU cycles.
Also, if a low priority process is in its critical section and a high priority process starts busy waiting, the latter will starve (never enter its critical section).
An attempt at a blocking solution (using OS help)
N items in buffer
shared int N, count
producer consumer
-------- --------
while (true) { while (true) {
produce_item(...); if (count == 0) sleep();
if (count == N) sleep(); remove_item(...);
enter_item(...); count--;
count++; if (count == N-1) wakeup(producer);
if (count == 1) wakeup(consumer); consume_item(...);
} }
if (count == 0) sleep(); compiles into
load count into accumulator
<--- context switch, producer sends wakeup
if (accumulator == 0) ...
Need to be able to store wakeup signals
SJH shartley@mcs.drexel.edu