Unit 2 Synchronization
Unit 2 Synchronization
Background
while (true)
{
/* produce an item in next produced */
while (count == BUFFER SIZE) ; /* do nothing */
buffer[in] = next_ produced;
in = (in + 1) % BUFFER SIZE;
count++;
}
Code for Consumer Process
while (true)
{
while (count == 0) ; /* do nothing */
next_ consumed = buffer[out];
out = (out + 1) % BUFFER SIZE;
count--;
/* consume the item in next consumed */
}
• Although 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 count
is currently 5 and that the producer and consumer processes
concurrently execute the statements “count++” and “count--”
• Following the execution of these two statements, the value of
the variable count may be 4, 5 or 6!
• The only correct result, though, is count == 5, which is
generated correctly if the producer and consumer execute
separately
• The value of count may be incorrect. It is shown as follows:
• The statement “count++” may be implemented in machine
language as follows:
register1 = count
register1 = register1 +1
count = register1
– Where register1 is one of the local CPU registers
• Similarly, the statement “count--” is implemented as follows:
register2 = count
register2 = register2 −1
count = register2
– Where again register2 is one of the local CPU registers
– nonpreemptive kernels
while (true){
flag[i] = true;
turn = j;
while (flag[j] && turn = = j);
/* critical section */
flag[i] = false;
/* remainder section */
}
The structure of process Pj in Peterson’s solution
while (true){
flag[j] = true;
turn = i;
while (flag[i] && turn = = i);
/* critical section */
flag[j] = false;
/* remainder section */
}
while (true){ while (true){
} }
• To enter the critical section, process Pi first sets flag[i] to be
true and then sets turn to the value j
– There by asserting that if the other process wishes to enter the critical
section, it can do so
• Thus, since Pi does not change the value of the variable turn
– int x = 0;
print x;
flag = true;
• The expected behavior is, of course, that Thread1 outputs the
value 100 for variable x
• However, as there are no data dependencies between the
variables flag and x, it is possible that a processor may reorder
the instructions for Thread 2 so that flag is assigned true
before assignment of x = 100
• In this situation, it is possible that Thread 1 would output 0 for
variable x
• Less obvious is that the processor may also reorder the
statements issued by Thread 1 and load the variable x before
loading the value of flag
• If this were to occur, Thread1 would output 0 for variable x
• How does this affect Peterson’s solution?
• Consider what happens if the assignments of the first two
statements that appear in the entry section of Peterson’s
solution are reordered
The structure of process Pi in Peterson’s solution
while (true){
flag[i] = true;
turn = j;
while (flag[j] && turn = = j);
/* critical section */
flag[i] = false;
/* remainder section */
}
• It is possible that both threads may be active in their critical
sections at the same time as shown in the figure below:
acquire lock
critical section
release lock
remainder section
}
• The definition of acquire() is as follows:
• acquire()
{
while (!available) ; /* busy wait */
available = false;
}
• The definition of release() is as follows:
• release()
{
available = true;
}
• Calls to either acquire() or release() must be performed
atomically
• The main disadvantage of the implementation given here is
that it 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 continual looping is clearly a problem in a real
multiprogramming system, where a single CPU core is shared
among many processes
• Busy waiting also wastes CPU cycles that some other process
might be able to use productively
• The type of mutex lock that is described here is also called a
spinlock because the process “spins” while waiting for the
lock to become available
• 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
• In certain circumstances on multicore systems, spinlocks are
in fact the preferable choice for locking
• If a lock is to be held for a short duration, one thread can
“spin” on one processing core while another thread performs
its critical section on another core
• On modern multicore computing systems, spinlocks are
widely used in many operating systems
Semaphores
• wait(S) {
monitor monitor-name
{
// shared variable declarations
procedure P1 (…) { …. }
procedure P2 (…) { …. }
Operating System Concepts – 10th Edition 6.54 Silberschatz, Galvin and Gagne ©2018
Schematic view of a Monitor
Operating System Concepts – 10th Edition 6.55 Silberschatz, Galvin and Gagne ©2018
Monitor Implementation Using Semaphores
Variables
semaphore mutex
mutex = 1
wait(mutex);
…
body of P;
…
signal(mutex);
Operating System Concepts – 10th Edition 6.56 Silberschatz, Galvin and Gagne ©2018
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()
• x.signal() – resumes one of processes (if any) that invoked
x.wait()
If no x.wait() on the variable, then it has no effect on the
variable
Operating System Concepts – 10th Edition 6.57 Silberschatz, Galvin and Gagne ©2018
Monitor with Condition Variables
Operating System Concepts – 10th Edition 6.58 Silberschatz, Galvin and Gagne ©2018
Usage of Condition Variable Example
Consider P1 and P2 that need to execute two statements S1 and S2
and the requirement that S1 to happen before S2
• Create a monitor with two procedures F1 and F2 that are
invoked by P1 and P2 respectively
• One condition variable “x” initialized to 0
• One Boolean variable “done”
• F1:
S1;
done = true;
x.signal();
• F2:
if done = false
x.wait()
S2;
Operating System Concepts – 10th Edition 6.59 Silberschatz, Galvin and Gagne ©2018
Monitor Implementation Using Semaphores
Variables
wait(mutex);
…
body of P;
…
if (next_count > 0)
signal(next)
else
signal(mutex);
Operating System Concepts – 10th Edition 6.60 Silberschatz, Galvin and Gagne ©2018
Implementation – Condition Variables
For each condition variable x, we have:
x_count++;
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x_sem);
x_count--;
Operating System Concepts – 10th Edition 6.61 Silberschatz, Galvin and Gagne ©2018
Implementation (Cont.)
if (x_count > 0) {
next_count++;
signal(x_sem);
wait(next);
next_count--;
}
Operating System Concepts – 10th Edition 6.62 Silberschatz, Galvin and Gagne ©2018
Resuming Processes within a Monitor
Operating System Concepts – 10th Edition 6.63 Silberschatz, Galvin and Gagne ©2018
Single Resource allocation
R.acquire(t);
...
access the resurce;
...
R.release;
Operating System Concepts – 10th Edition 6.64 Silberschatz, Galvin and Gagne ©2018
Single Resource allocation
R.acquire(t);
...
access the resurce;
...
R.release;
Operating System Concepts – 10th Edition 6.65 Silberschatz, Galvin and Gagne ©2018
A Monitor to Allocate Single Resource
monitor ResourceAllocator
{
boolean busy;
condition x;
void acquire(int time) {
if (busy)
x.wait(time);
busy = true;
}
void release() {
busy = false;
x.signal();
}
initialization code() {
busy = false;
}
}
Operating System Concepts – 10th Edition 6.66 Silberschatz, Galvin and Gagne ©2018
Single Resource Monitor (Cont.)
Usage:
acquire
...
release
Incorrect use of monitor operations
• release() … acquire()
• acquire() … acquire())
• Omitting of acquire() and/or release()
Operating System Concepts – 10th Edition 6.67 Silberschatz, Galvin and Gagne ©2018
Classical Problems of Synchronization
• Examples
– The Readers–Writers Problem
– semaphore mutex = 1;
– int read_count = 0;
while (true) {
wait(rw_mutex);
...
/* writing is performed */
...
signal(rw_mutex);
}
• The code for a reader process is shown in Figure below:
• While (true)
• {
• wait(mutex); // Reader wants to enter the critical section
• read_count++; // The number of readers has now increased
by 1
• // there is atleast one reader in the critical section
• // this ensure no writer can enter if there is even one reader
• // thus we give preference to readers here
• if (read_count==1)
– wait(rw_mutex);
while (true){
wait (chopstick[i] );
wait (chopStick[ (i + 1) % 5] );
signal (chopstick[i] );
signal (chopstick[ (i + 1) % 5] );
}
• Although this solution guarantees that no two neighbours are
eating simultaneously, it nevertheless must be rejected
because it could create a deadlock
• Suppose that all five philosophers become hungry at the same
time and each grabs her left chopstick
• All the elements of chopstick will now be equal to 0
• When each philosopher tries to grab her right chopstick, she
will be delayed forever
• Several possible remedies to the deadlock problem are the
following:
– Allow at most four philosophers to be sitting simultaneously at the
table
– Allow a philosopher to pick up her chopsticks only if both chopsticks
are available
– Use an asymmetric solution that is, an odd-numbered philosopher
picks up first her left chopstick and then her right chopstick, whereas
an even numbered philosopher picks up her right chopstick and then
her left chopstick
Monitor Solution to Dining Philosophers
monitor DiningPhilosophers
{
enum {THINKING; HUNGRY, EATING} state [5];
condition self [5];
Operating System Concepts – 10th Edition 7. Silberschatz, Galvin and Gagne ©2018
Solution to Dining Philosophers (Cont.)
initialization_code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
Operating System Concepts – 10th Edition 7. Silberschatz, Galvin and Gagne ©2018
Solution to Dining Philosophers (Cont.)
Each philosopher “i” invokes the operations pickup() and
putdown() in the following sequence:
DiningPhilosophers.pickup(i);
/** EAT **/
DiningPhilosophers.putdown(i);
No deadlock, but starvation is possible
Operating System Concepts – 10th Edition 7. Silberschatz, Galvin and Gagne ©2018
Thank you