Concurrency and Synchronization
Concurrency and Synchronization
Process Synchronization
Process Synchronization means sharing system resources by processes in such a way that
Concurrent access to shared data is handled thereby minimizing the chance of inconsistent
data. Maintaining data consistency demands mechanisms to ensure synchronized execution of
cooperating processes.
Process Synchronization was introduced to handle problems that arose while multiple process
executions.
On the basis of synchronization, processes are categorized as one of the following two types:
● Independent Process : Execution of one process does not affect the execution of other
processes.
● Cooperative Process : Execution of one process affects the execution of other
processes.
Process synchronization problem arises in the case of Cooperative processes also because
resources are shared in Cooperative processes.
Race Condition
When more than one processes are executing the same code or accessing the same memory
or any shared variable in that condition there is a possibility that the output or the value of the
shared variable is wrong so for all the processes doing race to say that my output is correct this
condition is known as race condition.
Several processes access and process the manipulations over the same data concurrently, then
the outcome depends on the particular order in which the access takes place.
Critical section is a code segment that can be accessed by only one process at a time. Critical
section contains shared variables which need to be synchronized to maintain consistency of
data variables.
1
In the entry section, the process requests for entry in the Critical Section.
Any solution to the critical section problem must satisfy three requirements:
● Mutual Exclusion : If a process is executing in its critical section, then no other process
is allowed to execute in the critical section.
● Progress : If no process is executing in the critical section and other processes are
waiting outside the critical section, then only those processes that are not executing in
their remainder section can participate in deciding which will enter in the critical section
next, and the selection can not be postponed indefinitely.
● Bounded Waiting : A bound must exist on the number of times that other processes are
allowed to enter their critical sections after a process has made a request to enter its
critical section and before that request is granted.
Peterson’s Solution
Peterson’s Solution is a classical software based solution to the critical section problem.
● boolean flag[i] :Initialized to FALSE, initially no one is interested in entering the critical
section
● int turn : The process whose turn is to enter the critical section.
2
Peterson’s Solution preserves all three conditions :
Mutual Exclusion is assured as only one process can access the critical section at any time.
Progress is also assured, as a process outside the critical section does not block other
processes from entering the critical section.
TestAndSet
0 Unlock
1 Lock
Before entering into the critical section, a process inquires about the lock. If it is locked, it keeps
on waiting till it becomes free and if it is not locked, it takes the lock and executes the critical
section.
3
In TestAndSet, Mutual exclusion and progress are preserved but bounded waiting cannot be
preserved.
Synchronization Hardware
Many systems provide hardware support for critical section code. The critical section problem
could be solved easily in a single-processor environment if we could disable interrupts to occur
while a shared variable or resource is being modified.
In this manner, we could be sure that the current sequence of instructions would be allowed to
execute in order without pre-emption. Unfortunately, this solution is not feasible in a
multiprocessor environment.
This message transmission lag delays entry of threads into critical sections and the system
efficiency decreases.
Mutex Locks
As the synchronization hardware solution is not easy to implement for everyone, a strict
software approach called Mutex Locks was introduced. In this approach, in the entry section of
code, a LOCK is acquired over the critical resources modified and used inside the critical
section, and in the exit section that LOCK is released.
As the resource is locked while a process executes its critical section hence no other process
can access it.
Introduction to Semaphores
In 1965, Dijkstra proposed a new and very significant technique for managing concurrent
processes by using the value of a simple integer variable to synchronize the progress of
interacting processes. This integer variable is called semaphore. So it is basically a
synchronizing tool and is accessed only through two low standard atomic operations, wait and
signal designated by P(S) and V(S) respectively.
In very simple words, semaphore is a variable which can hold only a non-negative Integer
value, shared between all the threads, with operations wait and signal, which work as follow:
P(S): if S ≥ 1 then S := S - 1
else <block and enqueue the process>;
4
V(S): if <some process is blocked on the queue>
then <unblock a process>
else S := S + 1;
Properties of Semaphores
1. It's simple and always has a non-negative Integer value.
2. Works with many processes.
3. Can have many different critical sections with different semaphores.
4. Each critical section has unique access semaphores.
5. Can permit multiple processes into the critical section at once, if desirable.
Types of Semaphores
Semaphores are mainly of two types:
1. Binary Semaphore:
It is a special form of semaphore used for implementing mutual exclusion, hence it is
often called a Mutex. A binary semaphore is initialized to 1 and only takes the values 0
and 1 during execution of a program.
2. Counting Semaphores:
These are used to implement bounded concurrency.
Example of Use
Here is a simple step wise implementation involving declaration and usage of semaphore.
5
.
.
End;
Limitations of Semaphores
1. Priority Inversion is a big limitation of semaphores.
2. Their use is not enforced, but is by convention only.
3. With improper use, a process may block indefinitely. Such a situation is called
Deadlock. We will be studying deadlocks in detail in coming lessons.
Below are some of the classical problems depicting flaws of process synchronization in systems
where cooperating processes are present.
6
wants to eat, he uses two chopsticks - one from their left and one from their right. When
a philosopher wants to think, he keeps down both chopsticks at their original place.
There is a buffer of n slots and each slot is capable of storing one unit of data. There are two
processes running, namely, producer and consumer, which are operating on the buffer.
A producer tries to insert data into an empty slot of the buffer. A consumer tries to remove data
from a filled slot in the buffer. As you might have guessed by now, those two processes won't
produce the expected output if they are being executed concurrently.
There needs to be a way to make the producer and consumer work in an independent manner.
7
Here's a Solution
One solution of this problem is to use semaphores. The semaphores which will be used here
are:
At any instant, the current value of empty represents the number of empty slots in the buffer and
full represents the number of occupied slots in the buffer.
do
{
// wait until empty > 0 and then decrement 'empty'
wait(empty);
// acquire lock
wait(mutex);
// release lock
signal(mutex);
// increment 'full'
signal(full);
}
while(TRUE)
● Looking at the above code for a producer, we can see that a producer first waits until
there is at least one empty slot.
● Then it decrements the empty semaphore because, there will now be one less empty
slot, since the producer is going to insert data in one of those slots.
● Then, it acquires lock on the buffer, so that the consumer cannot access the buffer until
the producer completes its operation.
● After performing the insert operation, the lock is released and the value of full is
incremented because the producer has just filled a slot in the buffer.
8
The Consumer Operation
The pseudocode for the consumer function looks like this:
do
{
// wait until full > 0 and then decrement 'full'
wait(full);
// acquire the lock
wait(mutex);
● The consumer waits until there is atleast one full slot in the buffer.
● Then it decrements the full semaphore because the number of occupied slots will be
decreased by one, after the consumer completes its operation.
● After that, the consumer acquires lock on the buffer.
● Following that, the consumer completes the removal operation so that the data from one
of the full slots is removed.
● Then, the consumer releases the lock.
● Finally, the empty semaphore is incremented by 1, because the consumer has just
removed data from an occupied slot, thus making it empty.
Consider there are five philosophers sitting around a circular dining table. The dining table has
five chopsticks and a bowl of rice in the middle as shown in the below figure.
9
Dining Philosophers Problem
At any instant, a philosopher is either eating or thinking. When a philosopher wants to eat, he
uses two chopsticks - one from their left and one from their right. When a philosopher wants to
think, he keeps down both chopsticks at their original place.
From the problem statement, it is clear that a philosopher can think for an indefinite amount of
time. But when a philosopher starts eating, he has to stop at some point of time. The
philosopher is in an endless cycle of thinking and eating.
while(TRUE)
{
wait(stick[i]);
/*
mod is used because if i=5, next
chopstick is 1 (dining table is circular)
*/
wait(stick[(i+1) % 5]);
/* eat */
signal(stick[i]);
signal(stick[(i+1) % 5]);
}
10
When a philosopher wants to eat the rice, he will wait for the chopstick at his left and picks up
that chopstick. Then he waits for the right chopstick to be available, and then picks it too. After
eating, he puts both the chopsticks down.
But if all five philosophers are hungry simultaneously, and each of them picks up one chopstick,
then a deadlock situation occurs because they will be waiting for another chopstick forever. The
possible solutions for this are:
● A philosopher must be allowed to pick up the chopsticks only if both the left and right
chopsticks are available.
● Allow only four philosophers to sit at the table. That way, if all the four philosophers pick
up four chopsticks, there will be one chopstick left on the table. So, one philosopher can
start eating and eventually, two chopsticks will be available. In this way, deadlocks can
be avoided.
There is a shared resource which should be accessed by multiple processes. There are two
types of processes in this context. They are reader and writer. Any number of readers can
read from the shared resource simultaneously, but only one writer can write to the shared
resource. When a writer is writing data to the resource, no other process can access the
resource. A writer cannot write to the resource if there are non zero number of readers
accessing the resource at that time.
The Solution
From the above problem statement, it is evident that readers have higher priority than writer. If a
writer wants to write to the resource, it must wait until there are no readers currently accessing
that resource.
Here, we use one mutex m and a semaphore w. An integer variable read_count is used to
maintain the number of readers currently accessing the resource. The variable read_count is
initialized to 0. A value of 1 is given initially to m and w.
Instead of having the process to acquire lock on the shared resource, we use the mutex m to
make the process to acquire and release lock whenever it is updating the read_count variable.
11
The code for the writer process looks like this:
while(TRUE)
{
wait(w);
signal(w);
}
And, the code for the reader process looks like this:
while(TRUE)
{
//acquire lock
wait(m);
read_count++;
if(read_count == 1)
wait(w);
//release lock
signal(m);
// acquire lock
wait(m);
read_count--;
if(read_count == 0)
signal(w);
// release lock
signal(m);
}
● As seen above in the code for the writer, the writer just waits on the w semaphore until it
gets a chance to write to the resource.
● After performing the write operation, it increments w so that the next writer can access
the resource.
12
● On the other hand, in the code for the reader, the lock is acquired whenever the
read_count is updated by a process.
● When a reader wants to access the resource, first it increments the read_count value,
then accesses the resource and then decrements the read_count value.
● The semaphore w is used by the first reader which enters the critical section and the last
reader which exits the critical section.
● The reason for this is, when the first readers enters the critical section, the writer is
blocked from the resource. Only new readers can access the resource now.
● Similarly, when the last reader exits the critical section, it signals the writer using the w
semaphore because there are zero readers now and a writer can have the chance to
access the resource.
Solution : The solution to this problem includes three semaphores.First is for the customer
which counts the number of customers present in the waiting room (customer in the barber chair
is not included because he is not waiting). Second, the barber 0 or 1 is used to tell whether the
barber is idle or is working, And the third mutex is used to provide the mutual exclusion which is
required for the process to execute. In the solution, the customer has the record of the number
13
of customers waiting in the waiting room if the number of customers is equal to the number of
chairs in the waiting room then the upcoming customer leaves the barbershop.
When the barber shows up in the morning, he executes the procedure barber, causing him to
block on the semaphore customers because it is initially 0. Then the barber goes to sleep until
the first customer comes up.
When a customer arrives, he executes the customer procedure the customer acquires the
mutex for entering the critical region, if another customer enters thereafter, the second one will
not be able to do anything until the first one has released the mutex. The customer then checks
the chairs in the waiting room if waiting customers are less then the number of chairs then he
sits otherwise he leaves and releases the mutex.
If the chair is available then the customer sits in the waiting room and increments the variable
waiting value and also increases the customer’s semaphore this wakes up the barber if he is
sleeping.
At this point, the customer and barber are both awake and the barber is ready to give that
person a haircut. When the haircut is over, the customer exits the procedure and if there are no
customers in the waiting room the barber sleeps.
Semaphore Customers = 0;
Semaphore Barber = 0;
Mutex Seats = 1;
int FreeSeats = N;
Barber {
while(true) {
14
/* waits for a customer (sleeps). */
down(Customers);
Customer {
while(true) {
/* protects seats so only 1 customer tries to sit
in a chair if that's the case.*/
down(Seats); //This line should not be here.
if(FreeSeats > 0) {
/* sitting down.*/
FreeSeats--;
15