Unit 2:Process Synchronization and deadlocks: The Critical
Section Problem, Synchronization hardware, Semaphores,
Classical problems of synchronization, Critical regions,
monitors, Dead locks – system model, Characterization,
Deadlock prevention, avoidance and detection, Recovery
from deadlock, Combined approach to deadlock handling.
Process Synchronization
When two or more process cooperates with each other, their order of execution
must be preserved otherwise there can be conflicts in their execution and
inappropriate outputs can be produced.
A cooperative process is the one which can affect the execution of other process or
can be affected by the execution of other process. Such processes need to be
synchronized so that their order of execution can be guaranteed.
The procedure involved in preserving the appropriate order of execution of
cooperative processes is known as Process Synchronization. There are various
synchronization mechanisms that are used to synchronize the processes.
Race Condition
●When several processes access and manipulate the same data
concurrently, the final outcome of the execution is not
predictable. The final outcome depends on the particular
order in which the access take place. This is called race
condition.
Critical Section
●The regions of a program that try to access shared resources
and may cause race conditions are called critical section. To
avoid race condition among the processes, we need to assure
that only one process at a time can execute within the critical
section.
The Critical Section Problem
Consider a system consisting on n processes
{p0,p1,p1,…,pn-1}
Each process has a segment of code ,called a critical section
In which the process may be changing common variable,
updating a table, writing a file and so on.
When one process is executing in its critical section ,no other
process is to be allowed to execute in its critical section.
That is ,no two processes are executing in their critical
sections at the same time.
Requirements of critical-section Problem
1.Mutual Exclusion: If process Pi is executing in its critical section,
then no other processes can be executing in their critical sections.
2.Progress: Progress means that if one process doesn't need to execute
into critical section then it should not stop other processes to get into
the critical section.
3.Bounded Waiting: There must exist a bound on the number of times
other processes can enter their critical sections after a process has
made a request to enter its critical section and before that request is
granted. This ensures that no process waits forever to enter the critical
section and prevents starvation.
Two process Solution for Critical –Section
Problem
Algorithm 1:
Turn Variable or Strict Alternation Approach is the software
mechanism implemented at user mode. It is a busy waiting
solution which can be implemented only for two processes.
This approach can only be used for only two processes. In general,
let the two processes be Pi and Pj. They share a variable called
turn variable.
For Process Pi
Non - CS
while (turn ! = i);
Critical Section
turn = j;
Non - CS
For Process Pj
Non - CS
while (turn ! = j);
Critical Section
turn = i ;
Non - CS
●There are only two values possible for turn variable, i or j. if its value is not i then it
will definitely be j or vice versa.
●In the entry section, in general, the process Pi will not enter in the critical section until
its value is j or the process Pj will not enter in the critical section until its value is i.
●Initially, two processes Pi and Pj are available and want to execute into critical section.
For Process Pi
●The turn variable is equal to i hence Pi will get the chance to enter into the critical
section. The value of Pi remains I until Pi finishes critical section.
For Process Pj
●Pi finishes its critical section and assigns j to turn variable. Pj will get the chance to
enter into the critical section. The value of turn remains j until Pj finishes its critical
section.
Process 0
while (true){
while (turn != 0) {
// Wait until it's Process 0's turn
}
// Critical section
// ...
turn = 1; // Give the turn to Process1
// Remainder section
// ...
}
Process 1
while (true) {
while (turn != 1) {
// Wait until it's Process 1's turn
}
// Critical section
// ...
turn = 0; // Give the turn back to Process 0
// Remainder section
// ... }
Analysis of Strict Alternation approach
●Mutual Exclusion
The strict alternation approach provides mutual exclusion in every
case. This procedure works only for two processes. The pseudo
code is different for both of the processes. The process will only
enter when it sees that the turn variable is equal to its Process ID
otherwise not Hence No process can enter in the critical section
regardless of its turn.
●Progress
Progress is not guaranteed in this mechanism. If Pi doesn't want to
get enter into the critical section on its turn then Pj got blocked for
infinite time. Pj has to wait for so long for its turn since the turn
variable will remain 0 until Pi assigns it to j.
While the Strict Alternation Approach is straightforward and effective for two
processes, it may not be the most efficient solution in cases where there are more
than two processes.
Petersons Solution
Shared variables:
int turn; // Indicates whose turn it is to enter the critical section (initially
0 or 1)
Boolean flag[2]; // A flag for each process to signal their intent to enter
the critical section
Process 0:
flag[0] = true; // Process 0 wants to enter the critical section
turn = 1; // Give the turn to Process 1
while (flag[1] && turn == 1) {
// Wait until it's Process 0's turn and Process 1 doesn't want to enter
}
// Critical section
// ...
flag[0] = false; // Process 0 is done with the critical section
// Remainder section
// ...
Process 1:
flag[1] = true; // Process 1 wants to enter the critical section
turn = 0; // Give the turn to Process 0
while (flag[0] && turn == 0) {
// Wait until it's Process 1's turn and Process 0 doesn't want to enter
}
// Critical section
// ...
flag[1] = false; // Process 1 is done with the critical section
// Remainder section
// ...
This algorithm ensures mutual exclusion, progress, and bounded waiting, which are the three
requirements of the critical-section problem.
Synchronization hardware
Hardware Locks are used to solve the problem of `process
synchronization. The process synchronization problem occurs when more
than one process tries to access the same resource or variable. If more
than one process tries to update a variable at the same time then a data
inconsistency problem can occur. This process synchronization is also
called synchronization hardware in the operating system.
Lock Variable-
●Lock variable is a synchronization mechanism.
●It uses a lock variable to provide the synchronization among the processes executing concurrently.
●However, it completely fails to provide the synchronization.
It is implemented as-
Initially, lock value is set to 0.
●Lock value = 0 means the critical section is currently vacant and no process is present inside it.
●Lock value = 1 means the critical section is currently occupied and a process is present inside it.
Scene-01:
●Process P
0
arrives.
●It executes the lock!=0 instruction.
●Since lock value is set to 0, so it returns value 0 to the while loop.
●The while loop condition breaks.
●It sets the lock value to 1 and enters the critical section.
●Now, even if process P
0
gets preempted in the middle, no other process can enter the critical section.
●Any other process can enter only after process P
0
completes and sets the lock value to 0.
Scene-02:
●Another process P
1
arrives.
●It executes the lock!=0 instruction.
●Since lock value is set to 1, so it returns value 1 to the while loop.
●The returned value 1 does not break the while loop condition.
●The process P
1
is trapped inside an infinite while loop.
●The while loop keeps the process P
1
busy until the lock value becomes 0 and its condition breaks.
Scene-03:
●Process P
0
comes out of the critical section and sets the lock value to 0.
●The while loop condition of process P
1
breaks.
●It sets the lock value to 1 and enters the critical section.
●Now, even if process P
1
gets preempted in the middle, no other process can enter the critical
section.
●Any other process can enter only after process P
1
completes and sets the lock value to 0.
Two commonly used hardware instructions are:
1.TestAndSet()
2.Swap()
TestAndSet()
●A hardware solution to the synchronisation problem.
●There is a shared lock variable each can take either of the
two values,0 or 1.
●Before entering into the critical section ,a process inquires
about the lock.
●If it is locked, it keeps on waiting till it becomes free.
●If it is not locked ,it takes the lock and executes the critical
section.
boolean testAndSet(boolean *lock) {
boolean oldValue = *lock; // Step 1: Read the current value of lock
*lock = true; // Step 2: Set lock to true (acquire the lock)
return oldValue; // Step 3: Return the old value of lock
}
Step 1: boolean oldValue = *lock;
●Reads the current value of the lock variable.
●This helps in checking whether the lock was false (unlocked) or true (already locked by another process).
Step 2: *lock = true;
●Sets the lock variable to true, indicating that the resource is now locked by the current process.
●If another process calls testAndSet(), it will see true and know that it must wait.
Step 3: return oldValue;
●Returns the old value of lock.
●If the old value was false, the process knows that it successfully acquired the lock.
●If the old value was true, it means another process is holding the lock, so the current process must wait.
boolean lock = false; // Shared lock variable
void process() {
while (testAndSet(&lock)); // Step 1: Keep checking until lock is false
// Critical Section (Only one process at a time)
// Access shared resource safely
lock = false; // Step 2: Release the lock after finishing
}
Step 1: while (testAndSet(&lock));
●Calls testAndSet(&lock).
●If the lock was false, it changes it to true and enters the critical section.
●If the lock was already true, the process keeps waiting (busy-waiting).
Step 2: Critical Section
●The process performs critical operations (e.g., updating shared data).
Step 3: lock = false;
●After finishing, the process releases the lock by setting it back to false.
●Now, another process can enter the critical section.
●The success of the mechanism in providing mutual exclusion lies in the test-and-set
instruction.
●Test-and-set instruction returns the old value of memory location (lock) and updates
its value to 1 simultaneously.
●The fact that these two operations are performed as a single atomic operation
ensures mutual exclusion.
●Preemption after reading the lock value was a major cause of failure of lock
variable synchronization mechanism.
●Now, no preemption can occur immediately after reading the lock value.
This synchronization mechanism guarantees mutual exclusion.
●After arriving, process executes the test-and-set instruction which
returns the value 0 to while loop and sets the lock value to 1.
●Now, no other process can enter the critical section until the process
that has begin the test-and-set finishes executing the critical section.
●Other processes can enter only after the process that has begin the
test-and-test finishes and set the lock value to 0.
●This prevents the occurrence of deadlock.
This synchronization mechanism guarantees freedom from deadlock.
●This synchronization mechanism may cause a process to starve for the CPU.
●There might exist an unlucky process which when arrives to execute the critical
section finds it busy.
●So, it keeps waiting in the while loop and eventually gets preempted.
●When it gets rescheduled and comes to execute the critical section, it finds
another process executing the critical section.
●So, again, it keeps waiting in the while loop and eventually gets preempted.
●This may happen several times which causes that unlucky process to starve for
the CPU.
This synchronization mechanism does not guarantee bounded wait
Semaphores are integer variables that are used to solve the critical section problem by
using two atomic operations, wait and signal that are used for process
synchronization.
The process of using Semaphores provides two operations:
●wait (P): The wait operation decrements the value of the semaphore
●signal (V): The signal operation increments the value of the semaphore.
When the value of the semaphore is zero, any process that performs a wait operation will be blocked until another process performs
a signal operation.
Counting Semaphores
Counting semaphores are signaling integers that can take on any integer
value. Using these Semaphores we can coordinate access to resources and
here the Semaphore count is the number of resources available. If the value
of the Semaphore is anywhere above 0, processes can access the critical
section or the shared resources.
The number of processes that can access the resources/code is the value of
the semaphore. However, if the value is 0, it means that there aren't any
resources that are available or the critical section is already being accessed
by a number of processes and cannot be accessed by more processes.
Counting semaphores are generally used when the number of instances of a
resource is more than 1, and multiple processes can access the resource.
Wait and Signal Operations in Semaphores
●Wait and Signal operations in semaphores are nothing but
those "P" and "V" functions
Wait Operation
●The wait operation, also called the "P" function, sleep, decrease, or down
operation, is the semaphore operation that controls the entry of a process into a
critical section. If the value of the mutex/semaphore is positive then we decrease
the value of the semaphore and let the process enter the critical section.
In pseudocode:
P(semaphore){
if semaphore is greater than 0
then decrement semaphore by 1
}
Signal Operation
●The function "V", or the wake-up, increase or up operation is the same as
the signal function, and as we know, once a process has exited the critical
section, we must update the value of the semaphore so that we can signal
the new processes to be able to access the critical section.
●For the updation of the value, once the process has exited the critical
section, since we had decreased the value of the semaphore by 1 in
the wait operation, here we simply increment it.
In pseudocode:
V(semaphore){
increment semaphore by 1
}
Implementation of Binary and Counting Semaphore in OS
●Let's take a look at the implementation of the semaphores with two
processes P1 and P2. For simplicity of the example, we will take 2 processes,
however, semaphores are used for a very large number of processes.
Binary Semaphores:
●Initially, the value of the semaphore is 1. When the process P1 enters the
critical section, the value of the semaphore becomes 0. If P2 would want to
enter the critical section at this time, it wouldn't be able to, since the value of
the semaphore is not greater than 0. It will have to wait till the semaphore
value is greater than 0, and this will happen only once P1 leaves the critical
section and executes the signal operation which increments the value of the
semaphore.
Classic problems of synchronization
1.Bounded Buffer (Producer-Consumer) Problem
2.Dining Philosophers Problem
3.The Readers Writers Problem
1.Bounded Buffer (Producer-Consumer) Problem
The Producer-Consumer Problem is a classic synchronization problem in operating
systems that involves two types of processes:
●Producer: Generates data and adds it to a shared buffer.
●Consumer: Retrieves data from the buffer and processes it.
Problem Statement
●The producer should not add data when the buffer is full.
●The consumer should not remove data when the buffer is empty.
●Both must access the shared buffer without conflicts (avoid race conditions).
Solution Approaches
1.Using Semaphores (Classical Solution)
○Three semaphores are used:
■mutex: Ensures mutual exclusion when accessing the buffer.
■empty: Tracks the number of empty slots in the buffer.
■full: Tracks the number of filled slots in the buffer.
○Producer waits if the buffer is full; Consumer waits if it is empty.
Algorithm:
●Producer:
1.Wait for an empty slot (empty--).
2.Acquire the mutex (mutex--).
3.Add data to the buffer.
4.Release the mutex (mutex++).
5.Signal that the buffer is full (full++).
●Consumer:
1.Wait for a filled slot (full--).
2.Acquire the mutex (mutex--).
3.Remove data from the buffer.
4.Release the mutex (mutex++).
5.Signal that a slot is empty (empty++).
Producer Consumer Problem
Bounded Buffer problem is also called producer consumer problem. This problem is
generalized in terms of the Producer-Consumer problem. Solution to this problem is,
creating two counting semaphores “full” and “empty” to keep track of the current
number of full and empty buffers respectively. Producers produce a product and
consumers consume the product, but both use of one of the containers each time.
●Problem : To make sure that the producer won’t try to add data into the
buffer if it’s full and that the consumer won’t try to remove data from an
empty buffer.
●Solution : The producer is to either go to sleep or discard data if the
buffer is full. The next time the consumer removes an item from the
buffer, it notifies the producer, who starts to fill the buffer again. In the
same way, the consumer can go to sleep if it finds the buffer to be empty.
The next time the producer puts data into the buffer, it wakes up the
sleeping consumer.
An inadequate solution could result in a deadlock where both processes
are waiting to be awakened.
Implementation using system calls
D
e
D
e
Decrement
Using Semaphores
Real-World Example
●Multithreading applications where threads share resources (e.g., a printer queue).
●Networking systems where data packets are produced and consumed.
●Database management systems handling concurrent read/write operations.
1.The Readers Writers Problem
The Reader-Writer Problem is a classic synchronization problem in operating systems
that deals with managing concurrent access to a shared resource (like a file or database).
Problem Statement
●Multiple readers can read the shared resource simultaneously.
●Only one writer can write at a time (no other readers or writers can access the
resource while writing).
●Readers and writers must be synchronized to avoid race conditions and ensure data
consistency.
Types of Reader-Writer Problems & Solutions
1.First Reader-Writers Problem (Reader Priority)
○Readers should not be blocked unless a writer is writing.
○Writers may starve if there are continuous readers.
○Solution: Use semaphores to ensure multiple readers can access the resource but
block new readers if a writer is waiting.
2.Second Reader-Writers Problem (Writer Priority)
○Writers should not starve (if a writer is waiting, new readers should wait).
○Readers may starve if writers keep coming.
○Solution: Give priority to writers by making readers wait if a writer is waiting.
3.Third Reader-Writers Problem (Fair Solution)
○Uses a queue-based approach to ensure fairness (first-come, first-served).
○Avoids starvation of both readers and writers.
Solution Using Semaphores (First Problem - Reader Priority)
We use the following semaphores:
●mutex: Ensures mutual exclusion when updating the reader count.
●rw_mutex: Ensures only one writer or multiple readers access the resource.
Algorithm
Reader Process:
1.Wait for mutex, increase read_count.
2.If it's the first reader, wait for rw_mutex (block writers).
3.Release mutex and read the resource.
4.After reading, wait for mutex and decrease read_count.
5.If it's the last reader, signal rw_mutex (allow writers).
6.Release mutex.
Writer Process:
1.Wait for rw_mutex (block all readers and writers).
2.Write to the resource.
3.Signal rw_mutex (allow others).
Reader Process
Steps:
1.Wait for mutex (so only one process updates read_count at a time).
2.Increase read_count (since a new reader is entering).
3.If it's the first reader (read_count == 1), wait for rw_mutex (block writers).
4.Release mutex, allowing other readers to update read_count.
5.Read the resource (multiple readers can read at the same time).
6.After reading, wait for mutex again (to update read_count).
7.Decrease read_count (reader is leaving).
8.If it's the last reader (read_count == 0), signal rw_mutex (allow writers).
9.Release mutex so other readers or writers can proceed.
If at least one reader is reading, writers are blocked.
Once the last reader finishes, writers can proceed.
Writer Process
Steps:
1.Wait for rw_mutex (blocks all other writers and readers).
2.Write to the resource (since only one writer is allowed).
3.Signal rw_mutex (allow waiting readers or writers to proceed).
A writer excludes both readers and other writers while writing.
Only one writer can write at a time.
Writers must wait until all readers finish.
Real-World Examples
●Databases where multiple users read data, but updates are done by a single
transaction at a time.
●File systems where multiple processes can read a file, but only one can modify it.
Readers and Writers Problem:
Suppose that a database is to be shared among several concurrent processes. Some of
these processes may want only to read the database, whereas others may want to
update (that is, to read and write) the database. We distinguish between these two
types of processes by referring to the former as readers and to the latter as writers.
Precisely in OS we call this situation as the readers-writers problem. Problem
parameters: One set of data is shared among a number of processes.
•Once a writer is ready, it performs its write. Only one writer may write at a time.
•If a process is writing, no other process can read it.
•If at least one reader is reading, no other process can write.
•Readers may not write and only read.
3.Dining Philosophers Problem
The Dining Philosophers Problem is a classic synchronization problem in operating
systems that demonstrates the challenges of deadlock, starvation, and concurrency
control.
Problem Statement
●There are five philosophers sitting around a circular table.
●Each philosopher alternates between thinking and eating.
●There are five forks, and each philosopher needs two forks (one on the left and one
on the right) to eat.
●Philosophers must pick up both forks before eating and must put them down after
eating.
●The challenge is to avoid deadlocks and starvation while ensuring all philosophers
get a chance to eat.
Common Issues
1.Deadlock
○If each philosopher picks up their left fork at the same time, they will all be waiting
for the right fork indefinitely.
2.Starvation
○Some philosophers may never get a chance to eat if others keep eating.
Solutions to Avoid Deadlock
1. Using Semaphores (Naïve Approach - Risk of Deadlock)
Each fork is represented by a semaphore (S[i]).
Algorithm
1.Wait for the left fork: wait(S[i])
2.Wait for the right fork: wait(S[(i+1) % 5])
3.Eat
4.Release the right fork: signal(S[(i+1) % 5])
5.Release the left fork: signal(S[i])
6.Think
✅ Problem: Deadlock can occur if all philosophers pick up their left fork at the same time.
2. Preventing Deadlock with an Arbitrator (Centralized Solution)
●A single mutex (waiter) controls access to forks.
●A philosopher must request permission before picking up forks.
●At most 4 philosophers can pick up a fork at the same time to ensure progress.
✅ Prevents deadlock by limiting concurrent philosophers.
3. Resource Hierarchy (Avoiding Circular Wait)
●Philosophers pick up the lower-numbered fork first.
●The last philosopher picks up the right fork first to break circular waiting.
✅ Deadlock is avoided by breaking circular dependency.
4. Asymmetric Solution
●Odd-numbered philosophers pick up the left fork first.
●Even-numbered philosophers pick up the right fork first.
✅ Ensures that not all philosophers wait for the same fork at the same time.
Solution to Avoid Deadlock
To break circular waiting, we can modify the approach:
Asymmetric Strategy Example
●Odd-numbered philosophers (P1, P3) pick up right fork first, then left fork.
●Even-numbered philosophers (P0, P2, P4) pick up left fork first, then right fork.
New Order of Execution
1.P0 picks F0 → picks F1 → Eats ??????
2.P1 picks F2 → picks F1 → Waits (F1 is held by P0)
3.P2 picks F2 → picks F3 → Eats ??????
4.P3 picks F4 → picks F3 → Waits (F3 is held by P2)
5.P4 picks F4 → picks F0 → Waits (F0 is held by P0)
➡ Since some philosophers eat and release forks, others can eventually proceed.
✅ Deadlock is prevented!
Real-World Examples
●Thread Synchronization: When multiple threads need access to shared
resources.
●Database Locking: Prevents multiple transactions from accessing the same data
simultaneously.
Dining Philosopher Problem
●The Dining Philosopher Problem states that K philosophers seated around a circular
table with one chopstick between each pair of philosophers. A philosopher may eat if he
can pickup the two chopsticks adjacent to him. One chopstick may be picked up by any
one of its adjacent followers but not both. This problem involves the allocation of
limited resources to a group of processes in a deadlock-free and starvation-free
manner.
The dining philosopher's problem is the classical problem of synchronization which
says that Five philosophers are sitting around a circular table and their job is to think
and eat alternatively. A bowl of noodles is placed at the center of the table along with
five chopsticks for each of the philosophers. To eat a philosopher needs both their
right and a left chopstick. A philosopher can only eat if both immediate left and right
chopsticks of the philosopher is available. In case if both immediate left and right
chopsticks of the philosopher are not available then the philosopher puts down their
(either left or right) chopstick and starts thinking again.
Five Philosophers sitting around the table
●Dining Philosophers Problem- Let's understand the Dining Philosophers
Problem with the below code, we have used fig 1 as a reference to make
you understand the problem exactly. The five Philosophers are
represented as P0, P1, P2, P3, and P4 and five chopsticks by C0, C1, C2,
C3, and C4.
Suppose Philosopher P0 wants to eat, it will enter in Philosopher()
function, and execute take_chopstick[i]; by doing this it holds C0
chopstick after that it execute take_chopstick[ (i+1) % 5; by doing this
it holds C1 chopstick( since i 0, therefore 0 1 % 5 1
Similarly suppose now Philosopher P1 wants to eat, it will enter in
Philosopher() function, and execute take_chopstick[i]; by doing this it
holds C1 chopstick after that it execute take_chopstick[ (i+1) % 5; by
doing this it holds C2 chopstick( since i 1, therefore 1 1 % 5 2
But Practically Chopstick C1 is not available as it has already been taken
by philosopher P0, hence the above code generates problems and
produces race condition.
The solution of the Dining Philosophers Problem
We use a semaphore to represent a chopstick and this truly acts as a solution of the
Dining Philosophers Problem. Wait and Signal operations will be used for the solution
of the Dining Philosophers Problem, for picking a chopstick wait operation can be
executed while for releasing a chopstick signal semaphore can be executed.
1. wait( S )
{
while( S <= 0) ;
S--;
}
2. signal( S )
{
S++;
}
The structure of the chopstick is an array of a semaphore which is
represented as shown below -
semaphore C5;
The drawback of the above solution of the dining philosopher problem
●From the above solution of the dining philosopher problem, we have proved that no
two neighboring philosophers can eat at the same point in time. The drawback of the
above solution is that this solution can lead to a deadlock condition. This situation
happens if all the philosophers pick their left chopstick at the same time, which leads
to the condition of deadlock and none of the philosophers can eat.
•Only in case if both the chopsticks ( left and right ) are available at the
same time, only then a philosopher should be allowed to pick their
chopsticks
•All the four starting philosophers ( P0, P1, P2, and P3) should pick the
left chopstick and then the right chopstick, whereas the last
philosopher P4 should pick the right chopstick and then the left
chopstick. This will force P4 to hold his right chopstick first since the
right chopstick of P4 is C0, which is already held by philosopher P0
and its value is set to 0, i.e C0 is already 0, because of which P4 will
get trapped into an infinite loop and chopstick C4 remains vacant.
Hence philosopher P3 has both left C3 and right C4 chopstick
available, therefore it will start eating and will put down its both
chopsticks once finishes and let others eat which removes the
problem of deadlock.
•Maximum number of philosophers on the table should not be more than
four, in this case, chopstick C4 will be available for philosopher P3, so P3
will start eating and after the finish of his eating procedure, he will put
down his both the chopstick C3 and C4, i.e. semaphore C3 and C4 will
now be incremented to 1. Now philosopher P2 which was holding
chopstick C2 will also have chopstick C3 available, hence similarly, he will
put down his chopstick after eating and enable other philosophers to eat.
•A philosopher at an even position should pick the right chopstick and
then the left chopstick while a philosopher at an odd position should pick
the left chopstick and then the right chopstick.