Brendan Ang

Search

Search IconIcon to open search

Process Synchronization

Last updated Nov 8, 2022 Edit Source

# Process Synchronization

Race Condition

# Critical Section Problem

One method to solve the race condition is to divide processes into critical sections which are segments that shared data is accessed. One process must be writing.

Problem: design protocol to ensure that no 2 processes are executing their critical section at the same time.

We need to satisfy 3 properties:

  1. Mutual exclusion: if process is executing in critical section, no other process can be executing in its critical section at the same time. *Why is mutual exclusion not enough?
    • It can be achieved naively by preventing any process from entering critical section
  2. Progress: if no process is executing in its critical section and another process needs to enter their critical section, selection of this process to enter cannot be postponed indefinitely
  3. Bounded waiting: if a process needs to enter their critical section, all other processes are allowed to enter their own critical section only a bounded number of times

# User-level Solutions

Following examples show how it is possible for 2 processes. For more processes, it becomes unfeasible

# Turn variable

Progress is violated:

  1. P1 finish critical section and pass the turn over to P0
  2. P0 finish its critical section and pass the turn over to P1
  3. P1 runs in a long remainder section and never passes turn back to P0
  4. Context switch occurs, P0 needs to enter critical section but P1 is stuck running remainder for a long period

# Flag variable

# Perterson’s Solution

Mutual exclusion:

# Hardware Solution

# Synchronization Hardware

Race condition is a result of context switches. We can prevent that in hardware to have atomic instructions. Difficult to control the disabling of context switches in user level as there may be many critical regions and regions execute for unknown amounts of time TestAndSet is now an assembly instruction which can be used to acquire a lock:

# Operating System Solution

# Mutex Locks

# Semaphore

# Busy waiting solution

Also known as a spinlock, where a thread trying to acquire a lock is caused to wait in a loop (“spin”) while repeatedly checking if the lock is available. Atomicity is not possible for this solution on a single-core. If a process P0 must loop to execute Wait(S), no other processes can execute Signal(S) in order to allow P0 to continue. If we cannot context switch then there is no solution.

# Blocking Solution

# Common Patterns

# Signalling

One thread sends a signal to another thread to indicate something has happened

1
2
aArrived = Semaphore(0)
bArrived = Semaphore(0)
Thread AThread B
statement a1statement b1
aArrived.signal()bArrived.signal()
bArrived.wait()aArrived.wait()
statement a2statement b2

# Classical Problems of Synchronization

# Bounded Buffer

The order of access to the shared variables matter. Logically, each process should check if the resource is available (in the case of consumer) OR if there is currently no instances (for producers), before trying to access the buffer through the mutex lock. Producer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
do 
{
    // wait until empty > 0 and then decrement 'empty'
    wait(empty);   
    // acquire lock
    wait(mutex);  
    
    /* perform the insert operation in a slot */
    
    // release lock
    signal(mutex);  
    // increment 'full'
    signal(full);   
} 
while(TRUE)

Consumer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
do 
{
    // wait until full > 0 and then decrement 'full'
    wait(full);
    // acquire the lock
    wait(mutex);  
    
    /* perform the remove operation in a slot */ 
    
    // release the lock
    signal(mutex); 
    // increment 'empty'
    signal(empty); 
} 
while(TRUE);

# Dining Philosophers

# Readers-Writers

Writer:

1
2
3
wait(wrt);
//write
signal(wrt);

# Second Readers-Writers Problem

It is possible that a reader R1 might have the lock, a writer W be waiting for the lock, and then a reader R2 requests access. It would be unfair for R2 to jump in immediately, ahead of W; if that happened often enough, W would  starve. Instead, W should start as soon as possible. This is the motivation for the second readers–writers problem, in which the constraint is added that no writer, once added to the queue, shall be kept waiting longer than absolutely necessary. This is also called writers-preference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int readcount, writecount;                   //(initial value = 0)
semaphore rmutex, wmutex, readTry, resource; //(initial value = 1)
//READER
reader() {
<ENTRY Section>
  readTry.P();                 //Indicate a reader is trying to enter
  rmutex.P();                  //lock entry section to avoid race condition with other readers
  readcount++;                 //report yourself as a reader
  if (readcount  1)          //checks if you are first reader
    resource.P();              //if you are first reader, lock  the resource
  rmutex.V();                  //release entry section for other readers
  readTry.V();                 //indicate you are done trying to access the resource
<CRITICAL Section>
//reading is performed
<EXIT Section>
  rmutex.P();                  //reserve exit section - avoids race condition with readers
  readcount--;                 //indicate you're leaving
  if (readcount  0)          //checks if you are last reader leaving
    resource.V();              //if last, you must release the locked resource
  rmutex.V();                  //release exit section for other readers
}
//WRITER
writer() {
<ENTRY Section>
  wmutex.P();                  //reserve entry section for writers - avoids race conditions
  writecount++;                //report yourself as a writer entering
  if (writecount  1)         //checks if you're first writer
    readTry.P();               //if you're first, then you must lock the readers out. Prevent them from trying to enter CS
  wmutex.V();                  //release entry section
  resource.P();                //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource
<CRITICAL Section>
  //writing is performed
  resource.V();                //release file
<EXIT Section>
  wmutex.P();                  //reserve exit section
  writecount--;                //indicate you're leaving
  if (writecount  0)         //checks if you're the last writer
    readTry.V();               //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading
  wmutex.V();                  //release exit section
}

# Practice Problems

a.

  1. Mutual exclusion
  2. Progression
  3. Bounded waiting b.
  1. use a boolean lock value initialized to false as a shared variable, and a register boolean as a flag
  2. continuously try to swap true into the lock
  3. if register becomes false, we got the lock
  4. critical section
  5. set the lock to false
1
2
3
4
5
6
7
8
9
while(1){
	register = true
	while (register) {
		swap(&lock, register)
	}  //entry section
	critical section...
	lock = false
	remainder section...
}

a. -4 b. -6. All Wait(S) runs before a single Signal(S). Each process is added to blocked queue until OS chooses to execute a critical region. c. 2. There can be at most 2 processes holding S simultaneously as 2 processes are able to complete wait(S) (not blocked) before the block list starts to increase. To ensure there is no deadlock, there should not be any nesting i.e. two semaphores are not acquired together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
wait(A)
apple++
signal(A)
wait(O)
if (at least 2 oranges in basket){
	oranges += 2
} else {
	signal(O)
	wait(A)
	apple--	
	signal(A)
}

Identify critical section for each function and use TestAndSet to acquire lock into the section:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Wait(S){
	while (TestAndSet(&lock));
	S.value--
	if (S.value < 0) {
		S.L = append(S.L, process)
		*lock = false
		sleep(process)
	} else {
		*lock = false
	}
}

Signal(S) {
	while (TestAndSet(&lock));
	S.value++
	if (S.value <= 0) {
		p = S.L[0]
		*lock = false
		//add to ready queue
	} else {
		*lock = false
	}
}