OS U3 ProcessCommunication 3
OS U3 ProcessCommunication 3
Synchronization
Chapter 3
Objective
3.1 Principles of concurrency, race condition, critical region
3.2 Mutual exclusion, semaphores, and mutex
3.3 Message passing and monitors
3.4 Classical problems of synchronization: Readers-writers problem, producer-
consumer problem, dining philosopher problem
3.5 Deadlock: Prevention, ignorance, avoidance, detection and recovery
Concurrency
Concurrency in operating systems refers to the capability of an OS to handle
more than one task or process at the same time, thereby enhancing efficiency
and responsiveness.
Concurrency can be achieved through multithreading or multi-processing
whereby more than one process or threads are executed simultaneously or in
an interleaved fashion.
The running process threads always communicate with each other through
shared memory or message passing.
Concurrency results in sharing of resources resulting in problems like
deadlocks and resources starvation.
Benefit of Concurrency
Resource Sharing and Performance
In concurrency, multiple programs run simultaneously while sharing system
resources such as the CPU and memory.
1. Its state is not shared with any 1. Its state is shared along other
other process. processes.
2. The result of execution depends 2. The result of the execution depends
only on the input state. on relative execution sequence and
3. The result of the execution will cannot be predicted in
always be the same for the same advanced(Non-deterministic).
input. 3. The result of the execution will not
4. The termination of the always be the same for the same
independent process will not input.
terminate any other. 4. The termination of the cooperating
process may affect other process.
Process Operation
Process Creation Process Termination
A parent process and then children of A child process can be terminated in the
that process can be created. following ways:
When more than one process is created A parent may terminate the execution of
several possible implementations exist. one of its children for a following reasons:
● Parent and child can execute ● The child has exceeded its allocation
concurrently. resource usage.
● The Parents waits until all of its ● The task assigned to its child is no
children have terminated. longer required.
● The parent and children share all ● If a parent has terminated than its
resources in common. children must be terminated.
● The children share only a subset of
their parent's resources.
Principles of Concurrency
Concurrency means multiple processes are in progress during the same time
period. Processes can execute concurrently in one of two ways:
A race condition is a situation that may occur inside a critical section. This happens when
the result of multiple process/thread execution in the critical section differs according to
the order in which the threads execute.
Two threads are incrementing the
same shared variable.
1. Read value
2. Add 1
3. Write back
1. Mutual Exclusion: Mutual exclusion means only one process can be inside the critical section at a time. If
other processes require the critical section, they must wait until it is free.
If Pi is executing in its critical section, no other processes can execute in their critical sections: The
resources involved are [Link] least one resource must be held in a non-shareable
mode i.e, only one process at a time claims exclusive control of the resource. If another process
requests that resource, it must be delayed until the resource is released.
2. Progress: Progress means that if a process is not using the critical section, it should not prevent other
processes from accessing it. In other words, any process can enter the critical section if it is free.
If the critical section is empty but processes are waiting to use it, only the processes that are actually
ready and waiting can compete for it. This group must choose one of them to go next without stalling or
waiting forever.
3. Bounded Waiting: Bounded waiting means that each process must have a limited waiting time and
should not wait endlessly to access the critical section. There exists a limit on the number of times
that order processes are allowed to enter critical section after a process has requested entry and before
that request is granted.
Solution to Critical Section Problem
A critical section is a code segment where a process accesses the section and then exits it, releasing the resources.
Synchronization Mechanisms
To ensure that only one process executes the critical section at a time, process synchronization mechanisms such as
semaphores are used. A semaphore is a variable that determines whether a resource is available and provides mutual
exclusion to shared resources.
Entry Protocol
When a process enters the critical section, it must first request access to the semaphore or mutex associated with the
section. If the resource is available, then the process can proceed to execute the critical section. If the resource is not
available, then the process must wait until it is released by the process currently executing the critical section.
Exit Protocol
When a process finishes executing the critical section, it releases the semaphore or mutex, allowing another process to enter
the critical section if needed.
Mutual Exclusion
Mutual exclusion ensures that only one process at a time can enter its critical section and access shared
resources.
● When more than one process wants to access or update a shared resource—meaning they want to enter their
critical sections—if they execute simultaneously, a race condition may occur, leading to inconsistent results. In
order to avoid race conditions, the process synchronization technique should satisfy the mutual exclusion
criteria.
In the critical section, if the executing process is interrupted by another process that updates the shared
resource in a different way, a race condition occurs and the value of the shared resource becomes invalid.
● Synchronization mechanisms are techniques that ensure processes are executed in such a manner that
the state of the system and common data structures remain consistent at all times.
● In order to avoid race conditions, synchronization techniques make sure that the instructions of the
operations in the critical section are executed as a single unit—in other words, in an atomic manner.
● Atomicity is guaranteed through mutual exclusion, through which only one process can execute in the
critical section at a particular time.
Requirements for Mutual Exclusion
For mutual exclusion to work properly, it must satisfy these four conditions:
1. Only one process can enter its critical section at a particular time.
2. A process is permitted to execute in its critical section only for a bounded
time.
3. No process that is not in its critical section can prevent another process from
entering its critical section.
4. No process must wait indefinitely to enter its critical section.
Techniques for Achieving Mutual Exclusion
Locks and Mutex
● Synchronization primitives called locks or mutex (short for mutual exclusion) are implemented to maintain
consistency of the value or status of shared resources.
● There are two possible states for a lock: locked and unlocked.
● An operating system or process must obtain the lock before being able to utilize the shared resource.
● The requesting process will be restricted as long as the lock has been released if it has already been locked by a
different thread.
Semaphores
A semaphore is an advanced synchronization tool that uses two atomic operations: wait() and signal(). The wait() instruction
is used in the entry section to gain access to the critical section, while the signal() operation is used to release control of the shared
resource.
Software-Based Techniques
Mutual exclusion can be achieved using a variety of software-based algorithms and strategies, including:
● Peterson's algorithm
● Dekker's algorithm
● Lamport's bakery algorithm
These techniques guarantee that only a single thread at one point is able to utilize the critical section by
combining factors, flags, and busy-waiting.
2. Lock
- Locks ensure that only one process at a time can enter the critical section.
- A process must “acquire” the lock before entering, and “release” it after exiting.
- If another process tries to enter while the lock is held, it must wait.
a. Software-Based Locks
b. Hardware-Based Locks
Software-Based Locks
Software-based locks are implemented using algorithms that rely on shared variables (like flags, turn
variables, or ticket numbers) to coordinate access to the critical section. These solutions try to ensure
mutual exclusion without depending on hardware instructions.
● Test-and-Set (TSL): Tests the lock’s old value and sets it to “locked” in one unbreakable step.
● Compare-and-Swap (CAS): Compares a memory value with an expected value and, if equal, swaps it with a new
value atomically.
● Spinlocks: Built using TSL or CAS; processes “spin” (busy wait) until the lock becomes free. Very fast for short
waits but wastes CPU cycles for long waits.
Limitations (why OS-based solutions are still needed):
● Processes may still use busy waiting, which wastes CPU cycles.
● No fairness guarantee one process might keep winning while others starve.
● Only solves basic mutual exclusion, not higher-level needs like sleeping, waking, or scheduling.
2.1 Mutex (Mutual Exclusion Lock)
Only one process is allowed to use a resource. It has a lock. If a process enters the shared resource, lock is
acquired and after finishing the task, the lock is release.
Components used in Mutex Locks
1. Mutex variable - which holds the state of a lock(If lock is available or not)
2. Lock Acquisition: Every process or thread will be requesting for a lock to utilize the shared resources. If lock is
available, the lock is granted else the process/thread will be in waiting state until the lock is released.
3. Lock Release: After completion of utilizing shared resource, then lock will be released.
A mutex is a higher-level lock abstraction often built on top of hardware primitives like spinlocks.
A spinlock is a synchronization mechanism in which a thread repeatedly checks for a lock in a loop until it
becomes available, instead of being blocked or put to [Link] and are widely used in thread libraries (e.g.,
Pthreads) and operating systems.
3. Semaphores and Monitors
Locks (software or hardware) solve the basic problem of mutual exclusion, but they still
have limitations:
● They rely on busy waiting, wasting CPU cycles.
● They don’t provide higher-level management like blocking, waking up, or handling
multiple conditions.
● Semaphores and
● Monitors.
These are built on top of lock-based mechanisms but provide extra control, safety and ease of use.
Semaphore
Semaphores proposed by Edsger Dijkstra, is a technique to manage concurrent processes by using a simple integer value, which is known as semaphore.
Semaphore is simply a variable which is non-negative and shared between threads. This variable is used to solve the critical section problem and to achieve process synchronization in the
multiprocessing environment.
A semaphore is an integer variable that, apart from initialization, is accessed only through two standard atomic operations:
● Can allow more than one process to access the resource simultaneously (counting semaphore).
● Can be binary (0 or 1), similar to a mutex.
P - denotes wait - test if there is any other process in the critical
section.
wait(): S - semaphor that is share resource, it is integer variable
when S is less than equal to 0, there is semicolon right after while,
no operation, but as long as condition is true, control will be inside
P(Semaphore S){ the while loop and not break the operation as long as the condition
does not become false.
while(S <= 0)
If S is greater than 0 - come out of while loop - decrement value of S.
; //no
operation ● This code signifies - S is shared. When one process wants to
enter critical section, S will take care of who should access the
S - -;
resource.
} ● If S is less than 0 - some process is already in critical section, no
other process is allowed in critical section. If
● S is greater than 0 then process will come out of loop,
decrement value of S and enter the critical section. Decrement
is necessary to let other process know, the condition will either
be true or false for that process.
signal()
V(Semaphore S){
S++;
}
Counting Semaphore
A semaphore that can hold any non-negative integer value, not just 0 and 1.
Counting Semaphore Example
Initially: S = 3
// P2 finishes printing
P2 calls signal(S) → S = -1 // S <= 0, so wake up P5
P5 gets printer and prints
// P3 finishes printing
P3 calls signal(S) → S = 0 // S <= 0, so wake up P6
P6 gets printer and prints
// All done
S = 3 again after all signal()
Features of Semaphores
Mutual Exclusion: Semaphore ensures that only one process accesses a
shared resource at a time.
Process Synchronization: Semaphore coordinates the execution order of
multiple processes.
Resource Management: Limits access to a finite set of resources, like printers,
devices, etc.
Reader-Writer Problem: Allows multiple readers but restricts the writers until
no reader is present.
Avoiding Deadlocks: Prevents deadlocks by controlling the order of allocation
of resources.
Types of Semaphores
Semaphores are mainly of two Types:
1. Counting Semaphore
Used when multiple instances of a resource exist.
The semaphore value can range over an unrestricted domain (0 to N).
Example: Managing access to a pool of 5 printers.
2. Binary Semaphore
Special case of counting semaphore with only two values: 0 and 1.
Works like a lock: either the resource is free (1) or busy (0).
Example: Managing access to a single critical section.
Mutex lock
Working of Semaphore
A semaphore works by maintaining a counter that controls access to a specific resource, ensuring
that no more than the allowed number of processes access the resource at the same time.
To achieve synchronization, every critical section of code is surrounded by two operations- Wait (P
operation) and Signal (V operation) that are discussed above.
The Operations
A counting semaphore uses two atomic operations:
wait() or P() - Decreases the counter when a process needs to acquire the resource
signal() or V() - Increases the counter when a process releases the resource
The Process Flow
When a thread needs to enter the critical area, the counting semaphore does
the following:
● Checks the counter - It looks at the current value of the counter
● Decreases the counter - It reduces the counter value by one
● Evaluates the result - If the counter decreases to a value that indicates
all resources are in use, the running thread becomes immobilized
● When Resources Are Unavailable: If the counter decreases and reaches
zero (or becomes negative), this signifies that the critical portion or
resource has become fully occupied. In this case:
■ The requesting thread is blocked and put into a waiting state
■ The thread is added to a queue of waiting processes
■ The thread will remain blocked until another thread releases the resource
Disadvantages of Semaphores
While semaphores are powerful synchronization tools, they come with several significant disadvantages that can make them challenging to use correctly and safely in
concurrent programming environments.
When a process cannot enter its critical section because the semaphore is not available, it continuously loops and checks the semaphore value without performing
any useful work.
The Problem
c
while (semaphore <= 0) {
// Continuously checking, doing nothing useful
2. Deadlock
Deadlock occurs when two or more processes are waiting indefinitely for each other to release resources that
they need. Each process holds a resource and waits for another resource held by another process.
The Problem
Complete system halt - Affected processes stop making progress
Difficult to detect - Deadlocks may not be immediately obvious
Requires external intervention - Often needs system restart or manual killing of processes
Example of Deadlock
Process A: wait(S) → wait(Q) → critical section → signal(Q) → signal(S)
Process B: wait(Q) → wait(S) → critical section → signal(S) → signal(Q)
The Problem
● Unfair resource allocation - Some processes never get to execute
● Priority inversion - Lower priority processes might get access while higher priority ones
wait
● Difficult to guarantee - No built-in mechanism in basic semaphores to ensure fairness
● Causes Improper queue management
● Processes with higher priority continuously acquiring the semaphore
● Poorly designed signaling mechanisms
Monitors
Monitors are a high-level synchronization mechanism that simplify process and
thread synchronization. They are built on top of locks and are mostly used in
multithreading systems like Java.
Unlike semaphores, where the programmer must explicitly call wait() and signal(),
monitors combine shared data and the operations on that data inside a single structure,
making synchronization safer and easier to manage.
Only one thread can execute inside a monitor at a time, ensuring automatic
mutual exclusion.
A monitor encapsulates both shared data (critical resource) and the operations that access or
modify it.
Mutual exclusion is enforced automatically, unlike semaphores where programmers must explicitly call
wait() and signal().
Condition variables are used to control thread waiting and signaling. The methods wait(), notify(), and
notifyAll() provide process coordination.
class
Structure of Monitor
Condition Variables in Monitors
A condition variable allows threads to wait until a certain condition becomes
true. They are always used inside a monitor. There are three main condition
variables:
wait(): temporarily releases the monitor lock and puts the thread to sleep until
it is signaled.
signal(): wakes up one waiting thread (if any).
broadcast() (in some languages): wakes up all waiting threads.
Message Passing
It refers to means of communication between
- Different thread with in a process .
- Different processes running on same node.
- Different processes running on different node.
In this a sender or a source process send a message to a non receiver or destination process.
Message has a predefined structure and message passing uses two system call: Send and Receive
1) Direct Addressing
2) Indirect Addressing
Direct Addressing
In this type that two processes need to name other to communicate. This become
easy if they have the same parent.
Example
If process A sends a message to process B, then
send(B, message);
Receive(A, message);
By message passing ,a link is established between A and B. Here the receiver knows
the Identity of sender message destination. This type of arrangement in direct
communication is known as Symmetric Addressing.
Contd..
Another type of addressing known as asymmetric addressing where receiver
does not know the ID of the sending process in advance.
Indirect Addressing
In this message send and receive from a mailbox. A mailbox can be abstractly viewed as an object into which
messages may be placed and from which messages may be removed by processes. The sender and receiver
processes should share a mailbox to communicate.
- One to One link: one sender wants to communicate with one receiver. Then single link is established.
- Many to Many link: Multiple Sender want to communicate with single [Link] in client server
system, there are many crying processes and one server process. The mailbox is here known as PORT.
- One to Many link: One sender wants to communicate with multiple receiver, that is to broadcast message.
Producer: The producer's job is to generate a bit of data, put it into the
buffer and start again.
● If the buffer is empty, then a consumer should not try to access the
data item from it.
● Similarly, a producer should not produce any data item if the buffer is
full.
Bounded Buffer Problem — Three Semaphore Solution
Producer Consumer
do{ do{
produce(item) wait(full) // block if buffer empty, if full>0
wait(mutex) // acquire lock
wait(empty) // block if buffer full, if empty > 0 there
are empty slots, empty = 0 no slots
wait(mutex) // acquire lock
item = buffer[out]
out = (out + 1) % N
buffer[in] = item
in = (in + 1) % N signal(mutex) // release lock
//here empty is decremented signal(empty) // notify producer
signal(mutex) // release lock
signal(full) // notify consumer, here full is //INCREMENT EMPTY SINCE WE TOOK AN ITEM
incremented }while (true)
}while (true)
Dining Philosophers Problem
The Dining Philosophers problem is a classic synchronization problem that
involves a certain number of philosophers sitting at a round table.
When a hungry philosopher has both his forks at the same time, he eats without releasing his forks. When he has
finished eating, he puts down both of his forks and starts thinking again.
Key challenges:
Deadlock: A situation where all philosophers hold one fork and wait for the other, causing a deadlock.
Starvation: A philosopher could potentially never get both forks and never eat
Dining Philosophers Problem:
Solutions:
• Resource hierarchy: Philosophers pick up forks in a specific order
(e.g., pick the lower-numbered fork first).
Writer Process
Do{
/*writer request for critical section*/
}while (true)
Solution for Readers:
do{
wait(mutex) // lock to safely update read_count, so that no two processes will update the read
//count at the same time.
read_count++
if read_count == 1: //at least one reader trying to read database
wait(wrt) // first reader blocks all writers, it will acquire write semaphore
if read_count == 0:
signal(wrt) // last reader releases writers
signal(mutex) // unlock read_count
}while (true)