Module Iii (Part 1)
Module Iii (Part 1)
Module Iii (Part 1)
Process synchronization- Race conditions – Critical section problem – Peterson’s solution, Synchronization
hardware, Mutex Locks, Semaphores, Monitors – Synchronization problems - Producer Consumer, Dining
Philosophers and Readers-Writers.
Producer Consumer Problem using bounded buffer
• The following variables reside in a region of memory shared by the
producer and consumer processes:
• The shared buffer is implemented as a circular array with two logical pointers in and out.
• The variable in points to the next free position in the buffer
• The variable out points to the first full position in the buffer.
Conditions:
Checked at Consumer
• buffer is empty when: Process
in == out
Checked at Producer
• buffer is full when: Process
((in + 1) % BUFFER SIZE) == out
Producer Process:
• The producer process has a local variable next produced in which the new
item to be produced is stored.
Consumer Process:
The consumer process has a local variable next consumed in which the item to be consumed is stored.
Process Synchronization: Overview
• Cooperating process is one that can affect or be affected by other
processes executing in the system.
• Concurrent access to shared data may result in data inconsistency.
• Various mechanisms to ensure the orderly execution of cooperating
processes that share a logical address space, so that data consistency
is maintained.
Consider Producer Consumer Problem:
• Modified algorithm with an integer variable counter, initialized to 0.
(counter=0)
• Counter is incremented every time we add a new item to the buffer
and is decremented every time we remove one item from the buffer.
Producer:
while (true) {
/* produce an item in next_Produced */
while (counter == BUFFER_SIZE); // do nothing
buffer [in] = next_Produced;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
Consumer:
while (true) {
while (counter == 0) ; // do nothing
next_Consumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
counter--;
/* consume the item in next_Consumed
}
• The producer and consumer routines shown above are correct
separately, they may not function correctly when executed
concurrently.
● Boolean flag[2]
• The flag array is used to indicate if a process is ready to enter the critical
section.
• flag[i] = true implies that process Pi is ready
Peterson’s Solution(Contn…)
• To enter the critical section, process Pi first sets flag[i] to be true and
then sets turn to the value j, thereby asserting that if the other
process wishes to enter the critical section, it can do so.
• If both processes try to enter at the same time, turn will be set to
both i and j at roughly the same time.
• Only one of these assignments will last; the other will occur but will
be overwritten immediately.
• The eventual value of turn determines which of the two processes is
allowed to enter its critical section first.
● Provable that the three CS requirement are met:
o Mutual exclusion is preserved
o Progress requirement is satisfied
o Bounded-waiting requirement is met
Synchronization Hardware
• Software-based solutions such as Peterson’s are not guaranteed to
work on modern computer architectures.
• The critical-section problem could be solved simply in a single-
processor environment if we could prevent interrupts from occurring
while a shared variable was being modified.
• In this way, we could be sure that the current sequence of
instructions would be allowed to execute in order without
preemption.
• No other instructions would be run, so no unexpected modifications
could be made to the shared variable.
• Unfortunately, this solution is not as feasible in a multiprocessor
environment.
• Disabling interrupts on a multiprocessor can be time consuming,
since the message is passed to all the processors.
• Many modern computer systems therefore provide special hardware
instructions:
• They allow us either to test and modify the content of a word or to
swap the contents of two words atomically—that is, as one
uninterruptible unit.
• Hardware instructions include the use of:
a)test and set()
b)compare and swap()
a)test and set()
• The important characteristic of this instruction is that it is executed
atomically.
• Thus, if two test and set() instructions are executed simultaneously
(each on a different CPU), they will be executed sequentially in some
arbitrary order.
• If the machine supports the test and set() instruction, then we can
implement mutual exclusion by declaring a boolean variable lock,
initialized to false.
b)compare and swap()
• Compare and swap() is executed atomically.
• The compare and swap() instruction, operates on three operands
• The operand value is set to new value only if the expression
(*value == expected) is true.
• compare and swap() always returns the original value of the variable
value.
Mutual exclusion can be provided as follows:
• a global variable (lock) is declared and is initialized to 0.
• The first process that invokes compare and swap() will set lock to 1.
• It will then enter its critical section, because the original value of lock
was equal to the expected value of 0.
• Subsequent calls to compare and swap() will not succeed, because
lock now is not equal to the expected value of 0.
• When a process exits its critical section, it sets lock back to 0, which
allows another process to enter its critical section.
Mutex Locks
• The hardware-based solutions to the critical-section problem are
complicated as well as generally inaccessible to application
programmers.
• Instead, operating-systems designers build software tools to solve the
critical-section problem.
• The simplest of these tools is the mutex lock. (mutex is short for
mutual exclusion.)
• We use the mutex lock to protect critical regions and thus prevent
race conditions.
• A process must acquire the lock before entering a critical section
• it releases the lock when it exits the critical section.
• The acquire() function acquires the lock, and the release() function
releases the lock.
• A mutex lock has:
• a boolean variable available
• indicates if the lock is available or not
1. If the lock is available:
• a call to acquire() succeeds
• the lock is then considered unavailable
2. If the lock is unavailable:
• A process that attempts to acquire an unavailable lock is blocked until the lock
is released.
• The definition of acquire() is as follows:
wait(S)
{
while (S <= 0); // busy wait
S--;
}
signal(S)
{
S++;
}
S=1
• All modifications to the integer value of the semaphore in the wait()
and signal() operations must be executed indivisibly.
• That is, when one process modifies the semaphore value, no other
process can simultaneously modify that same semaphore value.
• In addition, in the case of wait(S), the testing of the integer value of S
(S ≤ 0), as well as its possible modification (S--), must be executed
without interruption.
Semaphore usage
Two types of semaphores:
1. binary semaphores
2. counting semaphores
1. binary semaphore
signal(S)
{
S++;
}
2. Counting semaphores
• value of a counting semaphore can range over an unrestricted domain.
• used to control access to a given resource consisting of a finite number of
instances.
• semaphore is initialized to the number of resources available.
• Each process that wishes to use a resource performs a wait() operation on the
semaphore (thereby decrementing the count).
• When a process releases a resource, it performs a signal() operation
(incrementing the count).
• When the count for the semaphore goes to 0, all resources are being used.
• After that, processes that wish to use a resource will block until the count
becomes greater than 0.
S=2
P2
Semaphore Implementation
● Busy waiting:
● While a process is in its CS, any other process that tries to enter
signal(semaphore *S) {
S->value++;
if (S->value <= 0)
remove a process P from S->list;
wakeup(P);
}
}
● Each semaphore has an integer value and a list of processes list.
● When a process must wait on a semaphore, it is added to the list of
processes.
● A signal() operation removes one process from the list of waiting
processes and awakens that process.
● The block() operation suspends the process that invokes it.
● The wakeup(P) operation resumes the execution of a blocked process P.
● These two operations are provided by the operating system as basic
system calls.
Deadlock and Starvation
●Full
●Mutex
do {
...
/* produce an item */
...
wait(empty); //wait until empty >0 and then decrement empty
wait(mutex); //acquire lock
...
/* add produced item to the buffer */
...
signal(mutex); //release lock
signal(full); //increment full
} while (true);
The structure of the consumer process:
do {
wait(full); //wait until full>0 and then decrement full
wait(mutex); //acquire lock
...
/* remove an item from buffer */
...
signal(mutex); //release lock
signal(empty); //increment empty
...
/* consume the item */
...
} while (true);
2.The Readers–Writers Problem
● Using semaphore
● An integer variable and 2 semaphores
● read_count
● Integer variable initialized to 0
● Keep track of how many processes are currently reading the object(Readers)
● Semaphore mutex
● Initialized to 1
● Used to ensure mutual exclusion when read_count is updated
● when any reader enters or exit from the critical section.
● Used only by reader process
● Semaphore rw_mutex
● initialized to 1
● Common to both reader and writer process
Structure of a writer process
do {
wait(rw_mutex);
...
/* writing is performed */
...
signal(rw_mutex);
} while (true);
Structure of a Reader process
do {
wait(mutex);
read_count++;// number of readers has now increased by 1
if (read_count == 1)
wait(rw_mutex);//ensure no writer can enter if there is even one reader
signal(mutex); //other readers can enter while this
//current reader is inside the critical section.
/* reading is performed */
wait(mutex);
read count--; // a reader wants to leave
if (read_count == 0) // no reader is left in the critical section
signal(rw_mutex); //writers can enter
signal(mutex); //reader leaves
} while (true);
3. Dining-Philosophers Problem
● Five philosophers are sitting around a circular
table and their job is to think and eat
alternatively.
● Bowl of noodles/rice is placed in front along with
five chopsticks for each of the philosophers.
● A philosopher can only eat if both immediate left
and right chopsticks of the philosopher is
available.
● A philosopher may pick up only one chopstick at
a time and cannot pick up a chopstick that is
already in the hand of a neighbor.
● When philosopher is finished eating, he puts
down both chopsticks and starts thinking again.
● Philosophers – processes
● Chop stick – limited resources shared between
processes
Solution of Dining Philosopher Problem using Semaphores
● Use a semaphore to represent a chopstick
do {
wait (chopstick[i]);
wait (chopstick[(i + 1) % 5] );
// eat
signal (chopstick[i]);
signal (chopstick[(i + 1) % 5] );
// think
} while (TRUE);
wait(S)
signal(S)
{
{
while (S <= 0);
S++;
S--;
}
}
• Thus the shared data are
semaphore chopstick[5];
where all the elements of chopstick are initialized to 1.
Problem:
● Although this solution guarantees that no two neighbors are eating simultaneously, it
could still create a deadlock.
● Suppose that all five philosophers become hungry simultaneously and each grabs their
left chopstick. All the elements of chopstick will now be equal to 0.
● When each philosopher tries to grab his right chopstick, he will be delayed forever.
Possible remedies:
Similar to syntax of
class in Java
Condition Construct:
● Condition variables: condition x, y;
dp.pickup(i);
eating
dp.putdown(i);