Module Iii (Part 1)

Download as pdf or txt
Download as pdf or txt
You are on page 1of 86

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.

• As an illustration, suppose that the value of the variable counter is


currently 5 and that the producer and consumer processes
concurrently execute the statements “counter++” and “counter--”.
• Following the execution of these two statements, the value of the
variable counter may be 4, 5, or 6!
• counter++ could be implemented as:
register1 = counter
register1 = register1 + 1
counter = register1

• counter-- could be implemented as:


register2 = counter
register2 = register2 - 1
counter = register2
Consider this execution interleaving with “counter = 5” initially:
S0: producer execute register1 = counter {register1 = 5}
S1: producer execute register1 = register1 + 1 {register1 = 6}
S2: consumer execute register2 = counter {register2 = 5}
S3: consumer execute register2 = register2 - 1 {register2 = 4}
S4: producer execute counter = register1 {counter = 6 }
S5: consumer execute counter = register2 {counter = 4}
• Notice that we have arrived at the incorrect state “counter == 4”,
indicating that four buffers are full
• If we reversed the order of the statements at S4 and S5, we would arrive at
the incorrect state “counter == 6”.
Race Condition
• We would arrive at this incorrect state because we allowed both
processes to manipulate the variable counter concurrently.
• A situation where several processes access and manipulate the same
data concurrently and the outcome of the execution depends on the
particular order in which the access takes place, is called a race
condition.
• To guard against the race condition above, we need to ensure that
only one process at a time can be manipulating the variable counter.
• To make such a guarantee, we require that the processes be
synchronized in some way.
Critical-Section Problem(CSP)
• Consider a system consisting of n processes {P0, P1, ..., Pn−1}.
• Critical section: Each process has a segment of code, called a critical
section, in which the process may be changing common variables,
updating a table, writing a file, and so on.
• when one process is executing in its critical section, no other process
is allowed to execute in its critical section.

No two processes are executing in their critical


sections at the same time.
• The critical-section problem(CSP) is to design a protocol that
the processes can use to cooperate.
• Each process must request permission to enter its critical section.
• The section of code implementing this request is the entry
section.
• The critical section may be followed by an exit section.
• The remaining code is the remainder section.
Solution to the critical-section problem
A solution to the critical-section problem must satisfy the following
three requirements:
1. Mutual exclusion
2. Progress
3. Bounded waiting
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.
If no process is executing in its critical section and some processes wish
to enter their critical sections, then only those processes that are not
executing in their remainder sections can participate in deciding which
will enter its critical section next, and this selection cannot be
postponed indefinitely.
3. Bounded waiting.
• There exists a bound, or limit, 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
• a classic software-based solution to the critical-section problem
known as Peterson’s solution.
• it provides a good algorithmic description of solving the critical-
section problem
• illustrates some of the complexities involved in designing software
that addresses the requirements of mutual exclusion, progress, and
bounded waiting.
• Restricted to two processes that alternate execution between their
critical sections and remainder sections.
• Peterson’s solution requires the two processes to share two data items:
int turn;
boolean flag[2];
● int turn
• The variable turn indicates whose turn it is to enter the critical section
• if turn =j, implies it is the turn of process pj to enter the CS

● 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:

• The definition of release() is as follows:


Disadvantage
busy waiting and spin lock
• requires busy waiting
• While a process is in its critical section, any other process that tries to
enter its critical section must loop continuously in the call to acquire().
• This type of mutex lock is also called a spinlock because the process
“spins” while waiting for the lock to become available.
• wastes CPU cycles that some other process might be able to use
productively.
Advantage
• Spinlocks do have an advantage, however, in that no context switch is
required when a process must wait on a lock, and a context switch
may take considerable time.
Semaphores
• Proposed by a Dutch Scientist, Edger Dijsktra
• more robust software based synchronization tool
• behave similarly to a mutex lock
• provide more sophisticated ways for processes to synchronize their
activities.
Semaphores(Contn..)
• Semaphore S
• an integer variable
• accessed only through two standard atomic operations:
1. wait() : P, from the Dutch word “proberen”, which means
to test.

2. signal(): V, from the Dutch word “verhogen”, which means


to increment.
● Definition of the wait() operation

wait(S)
{
while (S <= 0); // busy wait
S--;
}

● Definition of the signal() operation

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

• The value of a binary semaphore can range only between 0 and 1.


• 0 = lock
wait(S)
• 1= unlock {
while (S <= 0); // busy wait
• behave similarly to mutex locks S--;
}

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

● main problem : requires busy waiting

● Busy waiting:
● While a process is in its CS, any other process that tries to enter

its CS must loop continuously in the entry code.


● Busy waiting wastes CPU cycles that some other process might

be able to use productively.

● This type of semaphore is called spinlock


● Because process spins while waiting for the lock.
Solution to Busy Waiting in Semaphore
● modified the definition of wait() and signal() semaphore operations.
● Modification on wait()
● When a process executes the wait() operation and finds that the
semaphore value is not positive, it must wait.
● rather than engaging in busy waiting, the process can block itself.
● block() : places a process into waiting queue associated with the
semaphore, and the state of the process is switched to the waiting state
● Then the control is transferred to CPU scheduler, which selects another
process to execute.
● Modification on signal()
● A process that is blocked, waiting on a semaphore S, should be
restarted by a wakeup() operation when some other process
executes a signal() operation.
● changes the process from the waiting state to the ready state.
Semaphore Implementation with no Busy
waiting
● Semaphore should be defined as follows:
typedef struct{
int value;
struct process *list;
wait(semaphore *S) } semaphore;
{
S->value--;
if (S->value < 0)
{
add this process to S->list;
block();
}
}

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

● The implementation of semaphore with a waiting queue may result in


Deadlock:
● A situation where two or more processes are waiting indefinitely for

an event that can be caused by only one of the waiting processes.

● Let S and Q be two semaphores initialized to 1


● Priority Inversion –
● Scheduling problem when lower-priority process holds a lock needed by higher-
priority process
Classic Problems of Synchronization
• In this section, we present a number of synchronization problems as
examples
• These problems are used for testing nearly every newly proposed
synchronization scheme.
• In our solutions to the problems, we use semaphores for
synchronization, since that is the traditional way to present such
solutions.
Classic Problems of Synchronization
1. The Bounded-Buffer Problem / Producer-Consumer problem
2. The Readers–Writers Problem
3. The Dining-Philosophers Problem
1. The Bounded-Buffer Problem
● Also known as producer consumer problem

● The producer tries to insert data into an empty slot of a buffer.


● The consumer tries to remove data from a filled slot in the buffer.
Problem:
1. The producer must not insert data when the buffer is full.
2. The consumer must not remove data when the buffer is empty.
3. The producer and consumer should not insert and remove data simultaneously.
Solution to the Bounded-Buffer Problem using semaphore
● We assume that the pool consists of n buffers, each capable of holding one item.

We will make use of 3 semaphores:


●Empty

● Counting semaphore, used to keep track of empty slots

● initial value is the number of slots in the buffer

●Full

● Counting semaphore, used to keep track of number of filled slots in buffer.

● initialized to the value 0

●Mutex

● Binary semaphore which is used to acquire and release lock

● initialized to the value 1


The structure of the producer process:

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

• Suppose that a database is to be shared among several concurrent


processes:
• Reader Process: Some of these processes may want only to read the database.
• Writer Process: other processes may want to update (that is, to read and write) the
database.

• if two readers access the shared data simultaneously, no adverse effects


will result.
Problem:
• if a writer and some other process (either a reader or a writer) access the
database simultaneously, issues may arise.
The Readers–Writers Problem(Contn…)
Solution:
writers have exclusive access to the shared database while writing to
the database.
Solution to 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

● Since, 5 chopsticks – 5 semaphore is required


● semaphore chopstick[5] - all the elements of chopstick are initialized to 1
(free)
● Binary semaphore

● A chopstick can be picked up by executing a wait() operation on the semaphore


● Released by executing a signal() operation on the semaphore.
Structure of Philosopher i:

● Semaphore chopstick[5] : each initialized to 1 (free)

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:

1. Allow at most four philosophers to be sitting simultaneously at the table.


(count of chopstick remains 5 itself)

2. Allow a philosopher to pick up his chopsticks only if both chopsticks are


available.(to do this, he must pick them up in a critical section)

3. Use an asymmetric solution – that is,


1. an odd numbered philosopher picks up his left chopstick first and then
the right chopstick
2. Whereas, even numbered philosopher picks up his right chopstick first

and then the left one.


Monitors
● monitor type is an abstract data type—or ADT
● Can be used as an alternative to semaphores
● Provided by programming languages (like Java, C#..)
not by the OS.
● provides a convenient and effective mechanism for
process synchronization
● The monitor construct ensures that only one
process at a time is active within the monitor.
● includes a set of programmer defined operations that are provided with
mutual exclusion within the monitor:
● declares the variables along with the bodies of functions that operate on those
variables.
● A function defined within a monitor can access only those variables declared
locally within the monitor and its formal parameters.
● Similarly, the local variables of a monitor can be accessed by only the local
functions.
The syntax of a monitor type:

Similar to syntax of
class in Java
Condition Construct:
● Condition variables: condition x, y;

● Two operations are allowed on a condition variable:

● x.wait() – a process that invokes the operation is suspended until x.signal()

ie.If a process is already using a shared


variable,the requesting process has to wait in
the queue associates with the x condition.

● x.signal() – resumes one of processes (if any) that invoked x.wait().


Schematic view of a Monitor
Dining Philosophers Solution using Monitors
● Each philosopher i invokes the operations pickup() and putdown()
in the following sequence:

dp.pickup(i);

eating

dp.putdown(i);

● No deadlock, but starvation is possible

You might also like