Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
12 views48 pages

Process Communication and Synchronization

Chapter 3 discusses process communication and synchronization, focusing on concurrency and the challenges it presents, such as resource sharing, optimal allocation, and error detection. It explains critical sections, race conditions, and various solutions for achieving mutual exclusion, including disabling interrupts, strict alternation, and test-and-set locks. The chapter emphasizes the importance of ensuring that only one process can access shared resources at a time to prevent inconsistencies and errors.

Uploaded by

unik89025
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views48 pages

Process Communication and Synchronization

Chapter 3 discusses process communication and synchronization, focusing on concurrency and the challenges it presents, such as resource sharing, optimal allocation, and error detection. It explains critical sections, race conditions, and various solutions for achieving mutual exclusion, including disabling interrupts, strict alternation, and test-and-set locks. The chapter emphasizes the importance of ensuring that only one process can access shared resources at a time to prevent inconsistencies and errors.

Uploaded by

unik89025
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 48

Chapter 3: Process Communication and Synchronization

Principles of Concurrency
Concurrency is the tendency for things to happen at the same time in
a system. Process that coexists on the memory at a given time are called
concurrent process. The concurrent process may either be independent
or cooperating. The independent process, as the name implies do not
share any kind of information or data with each other. They just compete
with each other for resources like CPU, I/O devices etc.
In single processor multiprogramming systems, processes are
interleaved in the time to yield the appearances of simultaneous
execution. Even though actual parallelism is not achieved, and even
though there is a certain amount of overhead involved in switching back
and forth between processes, interleaved execution provides major
benefits in processing efficiency. In multiprocessor system, it is possible
not only to interleave the execution of multiple process but also to
overlap them.
At first glance, it may seem that Interleaving and overlapping represent
fundamentally different modes of execution and present different
problems. But in fact, both are examples of concurrent processing and
both represent same problems.
In concurrent processing following problems arises:
1. Sharing of global resources
For example: if two processes both make the use of same global
variables and both perform reads and writes on that variable, then the
order in which the various reads and writes are executed is critical.

2. Optimal resource allocation


For example, process A may request use of, and be granted control of, a
particular I/O channel and then be suspended before using the channel. It
may be undesirable for the OS simply to lock the channel and prevent its
use by other process; indeed, this may result in deadlock.
3. Locating programming errors
It becomes very difficult to locate a programming error because results
are typically not deterministic and reproducible.
A Simple Example
void echo()
{
chin = getchar();
chout = chin;
putchar(chout);
}
In case of single processor multiprogramming system
The sharing of main memory among processes is useful to permit
efficient and close interaction among processes. However, Consider the
following sequences:
1. Process P1 invokes the echo procedure and is interrupted
immediately after getchar() returns its value and stores it in chin.
At this point, the most recently entered character, x, is stored in
variable chin.
2. Process P2 is activated and invokes the echo procedure, which runs
to conclusion, inputting and then displaying a single character, y,
on the screen.
3. Process P1 is resumed. By this time, the value x has been
overwritten in chin and therefore lost. Instead, chin contains y,
which is transferred to chout and displayed.

Solution
Permit only one process at a time to be in that procedure. Then the
foregoing sequence would result in the following:
1. Process P1 invokes the echo procedure and is interrupted
immediately after the conclusion of the input function. At this
point, the most recently entered character, x, is stored in variable
chin.

2. Process P2 is activated and invokes the echo procedure.


However, because P1 is still inside the echo procedure, although
currently suspended, P2 is blocked from entering the procedure.
Therefore, P2 is suspended awaiting the availability of the echo
procedure.

3. At some later time, process P1 is resumed and completes


execution of echo. The proper character, x, is displayed.

4. When P1 exits echo, this removes the block on P2. When P2 is


later resumed, the echo procedure.

In case of multiple processor multiprogramming system


Processes P1 and P2 are both executing, each on a separate processor.
Both processes invoke the echo procedure.
The following events occur; events on the same line take place in
parallel:
1. Processes P1 and P2 are both executing, each on a separate processor.
P1 invokes the echo procedure.
2. While, P1 is inside the echo procedure, P2 invokes echo. Because P1
is still inside the echo procedure (whether P1 is suspended or executing),
P2 is blocked from entering the procedure. Therefore, P2 is suspended
awaiting the availability of the echo procedure.
3. At a later time, process P1 completes execution of echo, exits that
procedure, and continues executing. Immediately upon the exit of P1
from echo, P2 is resumed and begins executing echo.

In the case of a uniprocessor system, the reason we have a problem is


that an interrupt can stop instruction execution anywhere in a
process. In the case of a multiprocessor system, we have that same
condition and, in addition, a problem can be caused because two
processes may be executing simultaneously and both trying to access
the same global variable. However, the solution to both types of
problem is the same: control access to the shared resource.

Race Condition

A race condition is a situation that may occur inside a critical section.


This happens when the result of multiple thread execution in critical
section differs according to the order in which the threads execute.

Race conditions in critical sections can be avoided if the critical section


is treated as an atomic instruction. Also, proper thread synchronization
using locks or atomic variables can prevent race conditions.
int shared =5

Process 1 (P1) Process 2(P2)


Int x=shared int y = shared
x=x+1 y = y--
Sleep (1) Sleep (1)
Shared = x shared = y

Example:
A print spooler. When a process wants to print a file, it enters the file
name in a special spooler directory. Another process, the printer
daemon, periodically checks to see if there are any files to be printed,
and if there are, it prints them and then removes their names from the
directory.
Consider our spooler directory has a very large number of slots,
numbered 0, 1, 2 each one capable of holding a file name. Also imagine
that there are two shared variables, out, which points to the next file to
be printed, and in, which points to the next free slot in the directory.
These two variables might well be kept on a two-word file available to
all processes. At a certain instant, slots 0 to 3 are empty and slots 4 to 6
are full. More or less simultaneously, processes A and B decide they
want to queue a file for printing.
Process A reads in and stores the value, 7, in a local variable called
next-free slot. Just then a clock interrupt occurs and the CPU decides
that process A has run long enough, so it switches to process B. Process
B also reads in, and also gets a 7. It too stores it in its local variable
next-free slot. At this instant both processes think that the next available
slot is 7. Process B now continues to run. It stores the name of its file in
slot 7 and updates in to be an 8. Then it goes off and does other things.
Eventually, process A runs again, starting from the place it left off. It
looks at next- free slot, finds a 7 there, and writes its file name in slot 7,
erasing the name that process B just put there.
Then it computes next-free slot + 1, which is 8, and sets in to 8. The
spooler directory is now internally consistent, so the printer daemon will
not notice anything wrong, but process B will never receive any output.

Critical section
The critical section is a code segment where the shared variables can be
accessed. An atomic action is required in a critical section i.e. only one
process can execute in its critical section at a time. All the other
processes have to wait to execute in their critical sections.

A diagram that demonstrates the critical section is as follows:

In the above diagram, the entry section handles the entry into the critical
section. It acquires the resources needed for execution by the process.
The exit section handles the exit from the critical section. It releases the
resources and also informs the other processes that the critical section is
free.

We need four conditions to hold to have a good solution:


1. No two processes may be simultaneously inside their critical
regions.
2. No assumptions may be made about speeds or the number of
CPUs.
3. No process running outside its critical region may block other
processes.
4. No process should have to wait forever to enter its critical region.

Here process A enters its critical region at time T1. A little later, at time
T2 process B attempts to enter its critical region but fails because another
process is already in its critical region and we allow only one at a time.
Consequently, B is temporarily suspended until time T3 when A leaves
its critical region, allowing B to enter immediately. Eventually B leaves
(at T4) and we are back to the original situation with no processes in
their critical regions.
Solution to the Critical Section Problem
The critical section problem needs a solution to synchronize the different
processes. The solution to the critical section problem must satisfy the
following conditions:

1. Mutual Exclusion

Mutual exclusion implies that only one process can be inside the
critical section at any time. If any other processes require the
critical section, they must wait until it is free.

2. Progress

Progress means that if a process is not using the critical section,


then it should not stop any other process from accessing it. In other
words, any process can enter a critical section if it is free.

3. Bounded Waiting

Bounded waiting means that each process must have a limited


waiting time. It should not wait endlessly to access the critical
section.
Mutual Exclusion with Busy Waiting
In this section we will examine various proposals for achieving
mutual exclusion, so that while one process is busy updating
shared memory in its critical region, no other process will enter
its critical region and cause trouble.

Disabling Interrupts
On a single-processor system, the simplest solution is to have
each process disable all interrupts just after entering its critical
region and re-enable them just before leaving it. With interrupts
disabled, no clock interrupts can occur. The CPU is only
switched from process to process as a result of clock or other
interrupts, after all, and with interrupts turned off the CPU will
not be switched to another process. Thus, once a process has
disabled interrupts, it can examine and update the shared
memory without fear that any other process will intervene.
This approach is generally unattractive because it is unwise to
give user processes the power to turn off interrupts. Suppose that
one of them did it, and never turned them on again? That could
be the end of the system.
Furthermore, if the system is a multiprocessor (with two or
possibly more CPUs) disabling interrupts affects only the CPU
that executed the disable instruction. The other ones will
continue running and can access the shared memory.
Strict Alternation
Two Process Solution

#define FALSE 0 #define FALSE 0


#define TRUE 1 #define TRUE 1
while (TRUE) while (TRUE)
{ {
while (turn != 0) ; while (turn != 1);
critical_region(); critical _region();
turn = 1; turn = 0;
noncritical_region(); noncritical _region();
} }

Figure 2-23. A proposed solution to the critical region problem, (a) Process 0 (b) Process 1.

The integer variable turn, initially 0, keeps track of whose turn it


is to enter the critical region and examine or update the shared
memory. Initially, process 0 inspects turn, finds it to be 0, and
enters its critical region. Process 1 also finds it to be 0 and
therefore sits in a tight loop continually testing turn to see when
it becomes 1.
When process 0 leaves the critical region, it sets turn to 1, to
allow process 1 to enter its critical region. Suppose that process
1 finishes its critical region quickly, so that both processes are in
their noncritical regions, with turn set to 0. Now process 0
executes its whole loop quickly, exiting its critical region and
setting turn to 1. At this point turn is 1 and both processes are
executing in their noncritical regions.
Lock variable
Consider having a single, shared (lock) variable, initially 0.
When a process wants to enter its critical region, it first tests the
lock. If the lock is 0, the process sets it to 1 and enters the
critical region. If the lock is already 1, the process just waits
until it becomes 0. Thus, a 0 means that no process is in its
critical region, and a 1 means that some process is in its critical
region.

The pseudo code of the mechanism looks like following.


Entry Section →
While (lock! = 0);
Lock = 1;
//Critical Section
Exit Section →
Lock =0;

Unfortunately, this idea contains flaw that violates mutual


exclusion. Suppose that one process reads the lock and sees that
it is 0. Before it can set the lock to 1, another process is
scheduled, runs, and sets the lock to 1. When the first process
runs again, it will also set the lock to 1, and two processes will
be in their critical regions at the same time.

Here’s the assembly code of the lock implementation

1. Load R0, Lock


2. CMP R0, #0
3. JNZ Step 1
4. Store Lock, #1
5. Critical section
6. Store Lock, #0

Test and set lock (TSL)

• Test and Set Lock (TSL) is a synchronization mechanism.


• It uses a test and set instruction to provide the
synchronization among the processes executing concurrently.
Test-and-Set Instruction
It is an instruction that returns the old value of a memory
location and sets the memory location value to 1 as a single
atomic operation.
If one process is currently executing a test-and-set, no other
process is allowed to begin another test-and-set until the first
process test-and-set is finished.

It is a hardware solution.

The assembly code of the solution will look like following.


Enter region: // before entering its critical section process calls enter region
1. TSL R0, Lock // copy lock variable to register and set lock to 1
2. CMP R0, #0 // is lock variable 0
3. JNZ step 1 // if it is not zero lock is set so loop
4. RET // return to caller; critical section entered

Leave region: // calls leave region when a process wants to leave a critical section

Move Lock, #0 // store 0 in lock variable


RET // return to caller
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 P0 arrives.
• It executes the test-and-set (Lock) instruction.
• Since lock value is set to 0, so it returns value 0 to the while
loop and sets the lock value to 1.
• The returned value 0 breaks the while loop condition.
• Process P0 enters the critical section and executes.
• Now, even if process P0 gets preempted in the middle, no
other process can enter the critical section.
• Any other process can enter only after process P0 completes
and sets the lock value to 0.

Scene-02:
• Another process P1 arrives.
• It executes the test-and-set (Lock) instruction.
• Since lock value is now 1, so it returns value 1 to the while
loop and sets the lock value to 1.
• The returned value 1 does not break the while loop condition.
• The process P1 is trapped inside an infinite while loop.
• The while loop keeps the process P1 busy until the lock value
becomes 0 and its condition breaks.

Scene-03:

Process P0 comes out of the critical section and sets the lock
value to 0.

• The while loop condition breaks.


• Now, process P1 waiting for the critical section enters the
critical section.
• Now, even if process P1 gets preempted in the middle, no
other process can enter the critical section.
• Any other process can enter only after process P1 completes
and sets the lock value to 0.
Characteristics-

The characteristics of this synchronization mechanism are-

• It ensures mutual exclusion.


• It is deadlock free.
• It does not guarantee bounded waiting and may cause
starvation.
• It suffers from spin lock.
• It is not architectural neutral since it requires the operating
system to support test-and-set instruction.
• It is a busy waiting solution which keeps the CPU busy when
the process is actually waiting.

Let's examine TSL on the basis of the four conditions.

o Mutual Exclusion

Mutual Exclusion is guaranteed in TSL mechanism since a


process can never be preempted just before setting the lock
variable. Only one process can see the lock variable as 0 at
a particular time and that's why, the mutual exclusion is
guaranteed.
o Progress

According to the definition of the progress, a process which


doesn't want to enter in the critical section should not stop
other processes to get into it. In TSL mechanism, a process
will execute the TSL instruction only when it wants to get
into the critical section. The value of the lock will always
be 0 if no process doesn't want to enter into the critical
section hence the progress is always guaranteed in TSL.

o Bounded Waiting

Bounded Waiting is not guaranteed in TSL. Some process


might not get a chance for so long. We cannot predict for a
process that it will definitely get a chance to enter in critical
section after a certain time.

o Architectural Neutrality

TSL doesn't provide Architectural Neutrality. It depends on


the hardware platform. The TSL instruction is provided by
the operating system. Some platforms might not provide
that. Hence it is not Architectural natural.
Peterson’s solution to critical section problem
• Peterson's solution is a classic software-based solution to
the critical-section problem.
• It provides a good algorithmic description of solving the
critical-section problem that addresses the requirements of
mutual exclusion, progress, and bounded waiting
requirements.
• Peterson's solution is restricted to two processes that
alternate execution between their critical sections and
remainder sections.
• Peterson's solution requires two data items to be shared
between the two processes:

int turn;

boolean flag[2]

where initially flag[i]=flag[j]=false

• The variable turn indicates whose turn it is to enter its


critical section. That is, if turn == i, then process Pi is
allowed to execute in its critical section.
• The flag array is used to indicate if a process is ready to
enter its critical section. For example, if flag[i] is true, this
value indicates that Pi is ready to enter its critical section.

Pi Pj

do { do {
flag[i]=true; flag[j]=true;
turn=j; turn=i;
while (flag[j] && turn==j); while (flag[i] && turn==i);
/* critical section */ /* critical section */
flag[i]=false; flag[j]=false;
/* remainder section */ /* remainder section */
} }
while (true); while (true);

Disadvantage
• Peterson’s solution works for two processes only.
• This solution is also a busy waiting solution so CPU time is
wasted. So that “SPIN LOCK” problem can come. And this
problem can come in any of the busy waiting solution.
Sleep and Wake up
For mutual exclusion one of the simplest is the pair sleep and wakeup.
Sleep is a system call that causes the caller to block, that is, be
suspended until another process wakes it up. The Wakeup call has one
parameter, the process to be awakened. Alternatively, both sleep and
wakeup each have one parameter, a memory address used to match up
sleeps with wakeups.

Semaphore
Semaphore is an integer variable that is used to solve the critical
section problem in mutual exclusive manner by using two atomic
operations, wait (down, P) and signal (up, V) that are used for process
synchronization.

A semaphore can only be accessed using the following operations:

• wait():- called when a process wants access to a resource


• signal():- called when a process is done using a resource

Types of Semaphores

There are two main types of semaphores i.e. counting semaphores and
binary semaphores. Details about these are given as follows:

1. Counting Semaphores
These are integer value semaphores and have an unrestricted value
domain. These semaphores are used to coordinate the resource
access, where the semaphore count is the number of available
resources. If the resources are added, semaphore count
automatically incremented and if the resources are removed, the
count is decremented.

2. Binary Semaphores

The binary semaphores are like counting semaphores but their


value is restricted to 0 and 1. The wait operation only works when
the semaphore is 1 and the signal operation succeeds when
semaphore is 0. It is sometimes easier to implement binary
semaphores than counting semaphores.

The definitions of wait and signal are as follows:

1. Wait

The wait operation decrements the value of its argument S, if it is


positive. If S is zero, then no operation is performed.

wait(S)
{
if S >=1 then S = S-0
else <block and enqueue the process>;
}
2. Signal

The signal operation increments the value of its argument S.


signal(S)
{
if (some process is blocked on the queue)
then <unblock a process>
else
S = S+1;
}

Now, let us see how it implements mutual exclusion. Let there be two
processes P1 and P2 and a semaphore s is initialized as 1. Now if
suppose P1 enters in its critical section then the value of semaphore s
becomes 0. Now if P2 wants to enter its critical section then it will wait
until s>0, this can only happen when P1 finishes its critical section and
calls signal() operation on semaphore S. This way mutual exclusion is
achieved. Look at the below image for details.
The producer-consumer problem
(Bounded buffer problem)

The Producer-Consumer problem is a classic problem which is used for


multi-process synchronization i.e. synchronization between more than
one processes.

In the producer-consumer problem, there is one Producer that is


producing something and there is one Consumer that is consuming the
products produced by the Producer. The producers and consumers share
the same memory buffer that is of fixed-size.
The job of the Producer is to generate the data, put it into the buffer, and
again start generating data. While the job of the Consumer is to consume
the data from the buffer.

What's the problem here?

• The producer should produce data only when the buffer is not full.
If the buffer is full, then the producer shouldn't be allowed to put
any data into the buffer.

• The consumer should consume data only when the buffer is not
empty. If the buffer is empty, then the consumer shouldn't be
allowed to take any data from the buffer.

• The producer and consumer should not access the buffer at the
same time.

The following are the solution to the problems that might occur in the
Producer-Consumer:

Producer consumer problem with sleep and wakeup:

The solution for the producer is to either go to sleep or discard data if


the buffer is full. When the consumer removes an item from the buffer, it
must notify the producer who start to fill the buffer again.
In the same way, consumer can go to sleep if it finds the buffer empty.
The next time the producer puts data into the buffer, it wakes up the
sleeping consumer.

The solution can be implemented using the IPC (inter-process


communication).

pseudocode
int itemcount
procedure producer()
{
while(true)
{
item=produce item();
if(itemcount=buffer_size) //buffer full
sleep;
put_item_into_buffer(item);
itemcount++;
if(itemcount=1)
wakeup(consumer);
}
}

procedure consumer()
{
while(true)
{
if(itemcount=0) //buffer empty
sleep;
item=remove_item_from_buffer(item);
itemcount--;
if(itemcount=buffer_size -1)
wakeup(producer);
}
}

The producer produces the item and inserts it into the buffer. The value
of the global variable count gets increased at each insertion. If the buffer
is filled completely and no slot is available then the producer will sleep,
otherwise it keeps inserting.

On the consumer's end, the value of count got decreased by 1 at each


consumption. If the buffer is empty at any point of time, then the
consumer will sleep otherwise, it keeps consuming the items and
decreasing the value of count by 1.

The consumer will be waked up by the producer if there is at least 1 item


available in the buffer which is to be consumed. The producer will be
waked up by the consumer if there is at least one slot available in the
buffer so that the producer can write that.

Well, the problem arises in the case when the consumer got preempted
just before it was about to sleep. Now, the consumer is neither sleeping
nor consuming. Since the producer is not aware of the fact that consumer
is not actually sleeping therefore it keep waking the consumer while the
consumer is not responding since it is not sleeping.
This leads to the wastage of system calls. When the consumer gets
scheduled again, it will sleep because it was about to sleep when it was
preempted.

The producer keeps writing in the buffer and it got filled after some
time. The producer will also sleep at that time keeping in the mind that
the consumer will wake him up when there is a slot available in the
buffer.

The consumer is also sleeping and not aware with the fact that the
producer will not wake him up.

This is a kind of deadlock where neither producer nor consumer is


active and waiting for each other to wake them up. This is a serious
problem which needs to be addressed.

Producer consumer problem with semaphore:


The above three problems can be solved with the help of semaphores.
In the producer-consumer problem, we use three semaphore variables:

1. Semaphore S: This semaphore variable is used to achieve mutual


exclusion between processes. By using this variable, either
Producer or Consumer will be allowed to use or access the shared
buffer at a particular time. This variable is set to 1 initially.
2. Semaphore E: This semaphore variable is used to define the
empty space in the buffer. Initially, it is set to the whole space of
the buffer i.e. "n" because the buffer is initially empty.

3. Semaphore F: This semaphore variable is used to define the


space that is filled by the producer. Initially, it is set to "0"
because there is no space filled by the producer initially.

By using the above three semaphore variables and by using


the wait() and signal() function, we can solve our
problem(the wait() function decreases the semaphore variable by 1 and
the signal() function increases the semaphore variable by 1). So, let's
see how:

The following is the pseudo-code for the producer:


#define N 100 //maximum slots in buffer
#define count=0 //items in the buffer
void producer()
{
int item;
while(T) {
item=produce_item();//generates item
wait(E); // decrement empty slot
wait(S); //set S to 0 i.e. enter into CS
append(); //add item to buffer
signal(S); //set S to 1 i.e. informing CS is free
signal(F); //increment full slot
}
}

The above code can be summarized as:

• while() is used to produce data, again and again, if it wishes to


produce, again and again.

• produce() function is called to produce data by the producer.

• wait(E) will reduce the value of the semaphore variable "E" by


one i.e. when the producer produces something then there is a
decrease in the value of the empty space in the buffer. If the
buffer is full i.e. the value of the semaphore variable "E" is "0",
then the program will stop its execution and no production will be
done.

• wait(S) is used to set the semaphore variable "S" to "0" so that no


other process can enter into the critical section.

• append() function is used to append the newly produced data in


the buffer.

• signal(s) is used to set the semaphore variable "S" to "1" so that


other processes can come into the critical section now because the
production is done and the append operation is also done.
• signal(F) is used to increase the semaphore variable "F" by one
because after adding the data into the buffer, one space is filled in
the buffer and the variable "F" must be updated.

This is how we solve the produce part of the producer-consumer


problem. Now, let's see the consumer solution. The following is the
code for the consumer:

void consumer() {
int item;
while(T)
{
wait(F); // decrement full counter
wait(S); //set S to 0 i.e. enter to CS
take(item); // consume item
signal(S); // set S to 1 i.e. informing CS is free
signal(E); // increment empty counter
use(); // make use of removed item if needed by any
process
}
}

The above code can be summarized as:

• while() is used to consume data, again and again, if it wishes to


consume, again and again.
• wait(F) is used to decrease the semaphore variable "F" by one
because if some data is consumed by the consumer, then the
variable "F" must be decreased by one.

• wait(S) is used to set the semaphore variable "S" to "0" so that no


other process can enter into the critical section.

• take() function is used to take the data from the buffer by the
consumer.

• signal(S) is used to set the semaphore variable "S" to "1" so that


other processes can come into the critical section now because the
consumption is done and the take operation is also done.

• signal(E) is used to increase the semaphore variable "E" by one


because after taking the data from the buffer, one space is freed
from the buffer and the variable "E" must be increased.

• use () is a function that is used to use the data taken from the
buffer by the process to do some operation.

Monitor

The monitor is one of the ways to achieve Process synchronization. The


monitor is supported by programming languages to achieve mutual
exclusion between processes. For example, Java Synchronized methods.
Java provides wait() and notify() constructs.
1. It is the collection of condition variables and procedures
combined together in a special kind of module or a package.
2. The processes running outside the monitor can’t access the internal
variable of the monitor but can call procedures of the monitor.
3. Only one process at a time can execute code inside monitors.

Condition Variables:
Two different operations are performed on the condition variables of the
monitor.
Wait.

signal.

let say we have 2 condition variables


condition x, y; // Declaring variable
Wait operation
x.wait() : Process performing wait operation on any condition variable
are suspended. The suspended processes are placed in block queue of
that condition variable.

Signal operation
x.signal(): When a process performs signal operation on condition
variable, one of the blocked processes is given chance.
Solution to Producer consumer problem using monitor

monitor producer_consumer
condition full, empty;
int count;
procedure enter:
begin;
if count=N then wait(full); // if buffer is full, block
else enter_item; // put item into the buffer
count++; // increment count of full slot
if count=1 then signal(empty); //if even one item added to the buffer, wake up consumer
end;
procedure remove:
begin
if count=0 then wait(empty); // if buffer is empty, do nothing
else remove_item; // remove an item from the buffer
count--; // decrement the count of full slots
if count=N-1 then signal(full) // if even single slot becomes empty, wake up producer
end;
count=0;
end monitor
procedure producer
begin;
while(true)
do
begin
produce_item
producer_consumer.enter
end
end
procedure consumer
begin
while(true)
do
begin
producer_consumer.remove
consume item;
end
end

Limitations
• Monitors have to be implemented as part of the programming
language. The compiler must generate code for them. This gives
the compiler the additional burden of having to know what
operating system facilities are available to control access to critical
sections in concurrent processes. Some languages that do support
monitors are Java, C#, Visual Basic, Ada and concurrent Euclid.
• Both semaphore and monitor are designed for solving the mutual
exclusion problem in the one or more CPUs’ that all have access to
common memory i.e. not suitable for system having private
memory.

Producer consumer problem using message passing


#define N 100 //no of slots in the buffer
void producer (void)
{
int item;
message m; // message buffer
while (true)
{
item = produce.item
receive (consumer, &m) //wait for an empty slot to arrive
build_message (&m, item) //construct a message to send
send(consumer,&m) //send item to consumer
}
}

void consumer (void)


{
int item, i;
message m; // message buffer
for (i=0; i<N; i++)
send (producer, &m); //send N empty
while (true)
{
receive (producer, &m); //get message containing item
item= extract_item(&m); //extract item from message
send (producer,&m); //send back empty reply
consume_item(item); //do something with the item
}
}

Dining Philosophers Problem


The dining philosopher’s problem is another classic synchronization
problem which is used to evaluate situations where there is a need of
allocating multiple resources to multiple processes.

What is the Problem Statement?


The dining philosophers’ problem states that there are 5 philosophers
sharing a circular table and they eat and think alternatively. There is a
bowl of rice for each of the philosophers and 5 chopsticks. A
philosopher needs both their right and left chopstick to eat. A hungry
philosopher may only eat if there are both chopsticks available.
Otherwise, a philosopher puts down their chopstick and begin thinking
again.
Fig: Dining Philosophers Problem

A solution of the Dining Philosophers Problem is to use a semaphore to


represent a chops tick. A chopstick can be picked up by executing a wait
operation on the semaphore and released by executing a signal operation
on semaphore. The structure of the chopstick is shown below:

Semaphore chopstick [5]


Initially the elements of the chopstick are initialized to 1 as the
chopsticks are on the table and not picked up by a philosopher.

The structure of a random philosopher i is given as follows:


do
{
wait (chopstick[i]);
wait (chopstick [(i+1) % 5]);
.
.
eating
.
.
signal ( chopstick[i]);
signal ( chopstick [(i+1) % 5]);

thinking
.
}
while (1);

In the above structure, first wait operation is performed on


chopstick[i] and chopstick [ (i+1) % 5]. This means that the philosopher
i has picked up the chopsticks on his sides. Then the eating function is
performed.

After that, signal operation is performed on chopstick[i] and


chopstick [ (i+1) % 5]. This means that the philosopher i has eaten and
put down the chopsticks on his sides. Then the philosopher goes back to
thinking.

Difficulty with the solution


The above solution makes sure that no two neighboring philosophers can
eat at the same time. But this solution can lead to a deadlock. This may
happen if all the philosophers pick their left chopstick simultaneously.
Then none of them can eat and deadlock occurs.

Some of the ways to avoid deadlock are as follows:

• There should be at most four philosophers on the table.


• Only independent process can eat at the same time.
• A philosopher should only be allowed to pick their chopstick if
both are available at the same time.
• For n-1 philosophers, execute the entry level code mentioned in the
above program but for nth philosopher flip the entry level code.
This will eventually block 5th philosopher from picking his left
chopstick and will be blocked. And 4th, 3rd, 2nd and 1st philosopher
can eat respectively. And deadlock will be removed.

Reader/writer problem
• Any number of reader activities and writer activities are
running.
• At any time, a reader activity may wish to read data.
• At any time, a writer activity may wish to write or modify
the data.
• Any number of readers may access the data simultaneously.
• During the time a writer is writing, no other reader or writer
may access the shared data.
• Readers – only read the data set they do not perform any
updates
• Writers – can both read and write
Fig: Readers-Writers with active readers
Fig: Readers-Writers with an active writer

Solution of reader writer problem using semaphore variable


There are two operations which we can be performed on semaphore
variable

1) wait(S) = S – 1
2) Signal(S) = S + 1

Here we used three semaphore variables to solve this problem:


1) Mutex initializes to 1 (Binary Semaphore)
2) DB initializes to 1 (Binary Semaphore)
3) Count initializes to 0 (Counting Semaphore)
• Mutex: Used to lock critical section for both readers and writers.
• DB: Used to block the writers from entering the critical section.
• Count: Helps counting the number of readers in the critical section
and permits the writer to enter when it becomes zero.

Int Count=0

Semaphore Mutex=1

Semaphore DB=1
Void reader(void) Void writer(void)
{ {
while (true) While(true)
{ {
Wait (mutex); Wait(DB);
Count= Count +1;
...................
If (Count == 1)
Then Wait(DB); Writing is performed
Signal(mutex);
...................
.................... Signal(DB);
Reading is performed }
}
...................

Wait(mutex)
Count= Count – 1;
If (Count == 0)
Then Signal(DB);
Signal(mutex);
Process data;
}
}
Sleeping Barber Problem
The sleeping barber problem is a classic inter-process
communication and synchronization problem between multiple operating
system processes.

❖ The barber shop has one barber, one barber chair, and n chairs for waiting
customers, if any, to sit on.
❖ If there are no customers present, the barber sits down in the barber chair
and falls asleep.
❖ When a customer arrives, he has to wake up the sleeping barber. If
additional customers arrive while the barber is cutting a customer’s hair,
they either sit down (if there are empty chairs) or leave the shop (if all chairs
are full).
❖ Our solution uses three semaphores:
▪ customers, which counts waiting customers (excluding the customer
in the barber chair, who is not waiting),
▪ barbers, state of barbers (0 or 1) based on barber is idle/active
▪ mutex, which is used for mutual exclusion.
❖ We also need a variable, waiting, which also counts the waiting customers. It
is essentially a copy of customers.
❖ In this solution, a customer entering the shop has to count the number of
waiting customers. If it is less than the number of chairs, he stays; otherwise,
he leaves.

#define CHAIRS 5 /* number of chairs for waiting customers */


typedef int semaphore;
semaphore customers = 0; /* track no of customers */
semaphore barbers = 0; /* No. of idle barber */
semaphore mutex = 1; /* for mutual exclusion */
int waiting = 0; /* customers are waiting not being haircut */
void Barber(void)
{
while (TRUE)
{
down(customers); /* go to sleep if number of customers is 0 */
down(mutex); /* acquire access to CS*/
waiting = waiting – 1; /* decrement count of waiting customers */
up(barbers); /* barber is now ready to cut hair */
up(mutex); /* release ‘waiting’ */
cut_hair(); /* cut hair */
}
}
void customer(void)
{
down(mutex); /* enter CS */
if (waiting < CHAIRS)
{
waiting = waiting + 1; /* increment count of customers */
up(customers); /* wake up barber if required */
up(mutex); /* release access to ‘waiting’ */
down(barbers); /* wait if barber is not free */
get_hairCut();
}
else
{
up(mutex); /* shop is full, do not wait */
}
}

❖ When the barber shows up for work in the morning, he executes the
procedure barber, causing him to block on the semaphore customers because
it is initially 0.
❖ The barber then goes to sleep. He stays asleep until the first customer
shows up.
❖ When a customer arrives, he executes customer, starting by acquiring mutex
to enter a critical region.
❖ If another customer enters shortly thereafter, the second one will not be able
to do anything until the first one has released mutex. The customer then
checks to see if the number of waiting customers is less than the number of
chairs.
❖ If not, he releases mutex and leaves without a haircut.
❖ If there is an available chair, the customer increments the integer variable,
waiting.
❖ Then he does an up on the semaphore customers, thus waking up the barber.
❖ At this point, the customer and barber are both awake.
❖ When the customer releases mutex, the barber grabs it, does some
housekeeping, and begins the haircut.
❖ When the haircut is over, the customer exits the procedure and leaves the
shop.

You might also like