Session 31
Session 31
Session 31
19CS2106R
Race Condition occurs when multiple process are trying to do something with shared data and the
final outcome depends on the order in which the processes run
Race Condition
This implementation is correct if executed in isolation. However, the code is not correct if
more than one copy executes concurrently. If two CPUs execute insert at the same time, it
could happen that both execute line 15 before either executes 16 (see Figure 4-1). If this
happens, there will now be two list nodes with next set to the former value of list. When
the two assignments to list happen at line 16, the second one will overwrite the first; the
node involved in the first assignment will be lost.
The lost update at line 16 is an example of a race condition. A race condition is a
situation in which a memory location is accessed concurrently, and at least one access is a
write. A race is often a sign of a bug, either a lost update (if the accesses are writes) or a
read of an incompletely-updated data structure. The outcome of a race depends on the
exact timing of the two CPUs involved and how their memory operations are ordered by
the memory system, which can make race-induced errors difficult to reproduce and debug.
Race Condition example – 2 i = 5 (shared)
• The problem is that in the time a single process takes to execute these three steps,
another process can perform the same three steps. Chaos can result, as we will see
in some examples that follow.
sequence-number-increment problem
#define MAXLINE 4096 /* max text line length */
#define SEQFILE "seqno" /* filename */ void
#define LOCKFILE "seqno.lock"
my_lock(int fd)
void my_lock(int), my_unlock(int);
{
int main(int argc, char **argv)
return;
{int fd;
}
long i, seqno;
pid_t pid;
ssize_t n; void
char line[MAXLINE + 1]; my_unlock(int fd)
pid = getpid(); {
fd = open(SEQFILE, O_RDWR, 0666); return;
for (i = 0; i < 20; i++) {
}
my_lock(fd); /* lock the file */
lseek(fd, 0L, SEEK_SET); /* rewind before read */
n = read(fd, line, MAXLINE);
line[n] = '\0'; /* null terminate for sscanf */
n = sscanf(line, "%ld\n", &seqno);
printf("%s: pid = %ld, seq# = %ld\n", argv[0], (long) pid, seqno);
seqno++; /* increment sequence number */
snprintf(line, sizeof(line), "%ld\n", seqno);
lseek(fd, 0L, SEEK_SET); /* rewind before write */
write(fd, line, strlen(line));
my_unlock(fd); /* unlock the file */
}
exit(0);
}
If the sequence number in the file is initialized to one, and a single copy of the program
is run, we get the following output:
[vishnu@team-osd ~]$ cc seqnumnolock.c When the sequence number is again initialized to
one, and the program is run twice
[vishnu@team-osd ~]$ vi seqno in the background, we have the following output:
[vishnu@team-osd ~]$ ./a.out [vishnu@team-osd ~]$ vi seqno
./a.out: pid = 5448, seq# = 1 [vishnu@team-osd ~]$ ./a.out & ./a.out &
[1] 7891
./a.out: pid = 5448, seq# = 2 [2] 7892
./a.out: pid = 5448, seq# = 3 [vishnu@team-osd ~]$ ./a.out: pid = 7892, seq# = 1 ./a.out: pid = 7892, seq# = 20
./a.out: pid = 5448, seq# = 4 ./a.out: pid = 7892, seq# = 2 ./a.out: pid = 7891, seq# = 20
./a.out: pid = 7892, seq# = 3
./a.out: pid = 5448, seq# = 5 ./a.out: pid = 7891, seq# = 21
./a.out: pid = 7892, seq# = 4
./a.out: pid = 5448, seq# = 6 ./a.out: pid = 7892, seq# = 5 ./a.out: pid = 7891, seq# = 22
./a.out: pid = 7892, seq# = 6 ./a.out: pid = 7891, seq# = 23
./a.out: pid = 5448, seq# = 7
./a.out: pid = 7892, seq# = 7 ./a.out: pid = 7891, seq# = 24
./a.out: pid = 5448, seq# = 8 ./a.out: pid = 7892, seq# = 8 ./a.out: pid = 7891, seq# = 25
./a.out: pid = 5448, seq# = 9 ./a.out: pid = 7892, seq# = 9 ./a.out: pid = 7891, seq# = 26
./a.out: pid = 5448, seq# = 10 ./a.out: pid = 7892, seq# = 10
./a.out: pid = 7891, seq# = 27
./a.out: pid = 7892, seq# = 11
./a.out: pid = 5448, seq# = 11 ./a.out: pid = 7892, seq# = 12 ./a.out: pid = 7891, seq# = 28
./a.out: pid = 5448, seq# = 12 ./a.out: pid = 7892, seq# = 13 ./a.out: pid = 7891, seq# = 29
./a.out: pid = 5448, seq# = 13 ./a.out: pid = 7892, seq# = 14 ./a.out: pid = 7891, seq# = 30
./a.out: pid = 7891, seq# = 8 ./a.out: pid = 7891, seq# = 31
./a.out: pid = 5448, seq# = 14 ./a.out: pid = 7892, seq# = 15 ./a.out: pid = 7891, seq# = 32
./a.out: pid = 5448, seq# = 15 ./a.out: pid = 7892, seq# = 16
./a.out: pid = 7891, seq# = 33
./a.out: pid = 5448, seq# = 16 ./a.out: pid = 7892, seq# = 17
./a.out: pid = 7891, seq# = 17 ./a.out: pid = 7891, seq# = 34
./a.out: pid = 5448, seq# = 17 ./a.out: pid = 7892, seq# = 18 ./a.out: pid = 7891, seq# = 35
./a.out: pid = 5448, seq# = 18 ./a.out: pid = 7891, seq# = 19 ./a.out: pid = 7891, seq# = 36
./a.out: pid = 5448, seq# = 19 ./a.out: pid = 7892, seq# = 19 [1]- Done ./a.out
./a.out: pid = 5448, seq# = 20 [2]+ Done ./a.out
Critical Section do {
entry section
• A critical section is a block of a
critical section
code that only one process at a
time can execute exit section
• The critical section problem is
to ensure that only one process } while (TRUE);
at a time is allowed to be
operating in its critical section
• Each process takes permission
from operating system to enter
into the critical section
The term critical section is used to refer to a section of code that accesses a shared resource and
whose execution should be atomic; that is, its execution should not be interrupted by another
thread that simultaneously accesses the same shared resource.
Mutual exclusion
• If a process is executing in its critical section , then no other process is
allowed to execute in the critical section
• No two process can be in the same critical section at the same time.
This is called mutual exclusion
Locks: The Basic Idea
• Ensure that any critical section executes as if it were a single
atomic instruction.
• An example: the canonical update of a shared variable
balance = balance + 1;
• Other threads are prevented from entering the critical section while the first
thread that holds the lock is in there.
Building A Lock
Efficient locks provided mutual exclusion at low cost.
Building a lock need some help from the hardware and the
OS.
Design
Evaluating locks – Basic criteria
• Mutual exclusion
• Does the lock work, preventing multiple threads
from entering a critical section?
• Fairness
• Does each thread contending for the lock get a
fair shot at acquiring it once it is free? (Starvation)
• Performance
• The time overheads added by using the lock
Controlling Interrupts
• Disable Interrupts for critical sections
• One of the earliest solutions used to provide mutual exclusion
• Invented for single-processor systems.
1 void lock() {
2 DisableInterrupts();
3 }
4 void unlock() {
5 EnableInterrupts();
6 }
• Problem:
• Require too much trust in applications
• Greedy (or malicious) program could monopolize the processor.
• Do not work on multiprocessors
• Code that masks or unmasks interrupts be executed slowly by modern CPUs
Why hardware support needed?
First attempt: Using a flag denoting whether the lock is held or
not.
The code below has problems.
1 typedef struct __lock_t { int flag; } lock_t;
2
3 void init(lock_t *mutex) {
4 // 0 lock is available, 1 held
5 mutex->flag = 0;
6 }
7
8 void lock(lock_t *mutex) {
9 while (mutex->flag == 1) // TEST the flag
10 ; // spin-wait (do nothing)
11 mutex->flag = 1; // now SET it !
12 }
13
14 void unlock(lock_t *mutex) {
15 mutex->flag = 0;
16 }
Why hardware support needed? (Cont.)
• Problem 1: No Mutual Exclusion (assume flag=0 to begin)
Thread1 Thread2
call lock()
while (flag == 1)
interrupt: switch to Thread 2
call lock()
while (flag == 1)
flag = 1;
interrupt: switch to Thread 1
flag = 1; // set flag to 1 (too!)
• Fairness: no
• Spin locks don’t provide any fairness guarantees.
• Indeed, a thread spinning may spin forever.
• Performance:
• In the single CPU, performance overheads can be quire painful.
• If the number of threads roughly equals the number of CPUs, spin locks work
reasonably well.
Load-Linked and Store-Conditional
• Test whether the value at the address(ptr) is equal to expected.
• If so, update the memory location pointed to by ptr with the new value.
• In either case, return the actual value at that memory location.
1 int CompareAndSwap(int *ptr, int expected, int new)
{
2 int actual = *ptr;
3 if (actual == expected)
4 *ptr = new;
5 return actual;
6 }