0% found this document useful (0 votes)
45 views55 pages

Synchronization in Concurrent Processes

Scheduling Processes
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)
45 views55 pages

Synchronization in Concurrent Processes

Scheduling Processes
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

Synchronization

Motivation
• Concurrent processes accessing shared data
• May result in data inconsistency
• Example reconsider the producer-consumer problem
• Simplifying assumption: A single processor system
• Remember the circular-array implementation of buffer
• Want to use full buffer
• Use a shared variable counter which keeps track of number of items in
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 */
}
Implementation of counter++ and counter--
register1 = counter counter++
register1 = register1 + 1
counter = register1

register2 = counter
register2 = register2 – 1 counter--
counter = register2
Race conditions
• Assuming the current value of counter is 5
• What will be the value if both producer and consumer run concurrently?
• Answer: 4, 5 or 6
• Why?
Race conditions
T0: producer execute register1 = counter {register1 = 5}
T1: producer execute register1 = register1 + 1 {register1 = 6}
T2: consumer execute register2 = counter {register2 = 5}
T3: consumer execute register2 = register2 − 1 {register2 = 4}
T4: producer execute counter = register1 {counter = 6}
T5: consumer execute counter = register2 {counter = 4}
The Critical Section Problem
• Each process has a so called critical section
• Changes the value of shared data
• Critical section problem: design a protocol that processes can use to
cooperate
Structure of a process
do {
entry section

critical section

exit section

remainder section

} while (true);
Constraints on solutions to the Critical
Section Problem
• Mutual exclusion
• Exactly one process may be executing in its critical section
• Progress
• Only process requesting entry into critical section may be considered for entry
• Bounded waiting
• Limit on the number of times entry request may be deferred
Race conditions in the kernel
• Preemptive kernels
• Possibility of race conditions – need an avoidance protocol
• Better system performance – a kernel process is not allowed to hold on to the
CPU for too long
• Nonpreemptive kernels
• No possibility of race conditions on single processor systems
• Poor system performance
Peterson’s Solution
• Restricted to two process that take turns in executing their critical
sections
do {
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
critical section
flag[i] = false;
remainder section
} while (true);
Synchronization hardware
• Locking
• One possibility is to disable interrupts
• Only works for single processor systems
• Inefficient for multicore systems – message has to be passed to all processors
• Adversely affects system clock, if it’s update is interrupt based
• Modern CPU architectures provide two atomic (uninterruptible)
operations
• test_and_set()
• compare_and_swap()
• Can use these to solve the critical section problem
test_and_set()
boolean test_and_set(boolean *target) {
boolean rv = *target;
*target = true;
return rv;
}

• target is a pointer (reference) parameter


• Changes to target are reflected in the actual parameter
test_and_set()
do {

while (test_and_set(&lock))
; /* do nothing */
/* critical section */
lock = false;
/* remainder section */

} while (true);
compare_and_swap()

int compare_and_swap(int *value, int expected, int new_value) {


int temp = *value;

if (*value == expected)
*value = new_value;

return temp;
}
compare_and_swap()
do {

while (compare_and_swap(&lock, 0, 1) != 0)
; /* do nothing */
/* critical section */
lock = 0;
/* remainder section */
} while (true);
Synchronization hardware
• The two algorithms just presented
• Guarantee mutual exclusion
• Do not guarantee bounded waiting
• Next algorithm guarantees both
do {
waiting[i] = true;
key = true;
while (waiting[i] && key)
key = test_and_set(&lock);
waiting[i] = false;
/* critical section */
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = false;
else
waiting[j] = false;
/* remainder section */
} while (true);
Mutex locks
• Mutex (= mutual exclusion) lock
• Software mechanism available to application programmers
• acquire() – process must successful execute this before entering CS
• release() – process must execute this on exit to free the lock
• available – Boolean indicating whether the lock is available
Mutex locks
acquire() {
while (!available)
; /* busy wait */

available = false;;
}

release() {
available = true;
}
Mutex locks
do {
acquire lock
critical section
release lock
remainder section
} while (true);
Mutex locks
• Disadvantage
• busy waiting – spinlock
• Wastes CPU cycles – a problem in a multiprogramming environment
•Spinlocks
• Can be useful for short waiting time
• When context-switching would consume more time
Semaphores
• Semaphore is an integer + two atomic operations
• wait()
• signal()
• Binary semaphore – domain is {0, 1}
• Same functionality as a mutex lock
• Counting semaphore – unrestricted domain
• Controls to a resource with finite instances
Semaphores
wait(S) {
while (S <= 0)
; // busy wait

S--;
}

signal(S) {
S++;
}
Binary semaphore – use case
• Synchronizing two processes
• Say P2 must execute statement S2 only after P1 executes statement S1
• Initialize semaphore synch to 0
P1{
...
S1;
signal(synch)
...
p2{
...
wait(synch);
S2
Semaphore + blocking wait()
typedef struct {
int value;
Linked list of processes
struct process *list;
waiting on the semaphore
} semaphore;
Semaphore + blocking wait()
wait(semaphore *S) {
S->value--;

if (S->value < 0) {
add this process to S->list;
block();
}
}
Semaphore + blocking wait()
signal(semaphore *S) {
S->value++;

if (S->value <= 0) {
remove a process P from S->list;
wakeup(P);
}
}
Semaphores – blocking wait()
• Both wait() and signal() must be executed atomically
• Critical sections!
• Atomicity can be guaranteed by
• Disabling interrupts – sensible only on single processor systems
• Use test_and_set() or compare_and_swap()
• Blocking wait does not completely eliminate busy waiting
Deadlocks
• Defn: A set of process (two or more processes in the set) are
deadlocked if each process in the set is waiting for another process in
the set to execute signal()
• Since the process that’s supposed to execute signal is blocked, all processes in
the set will wait indefinitely.
Deadlock example

P0 P1
wait(S); wait(Q);
wait(Q); wait(S);
. .
. .
. .
signal(S); signal(Q);
signal(Q); signal(S);
Indefinite blocking (starvation)
• A process may be indefinitely blocked if the queue on a semaphore is
processed in, say, LIFO
Priority Inversion
• Occurs on systems implementing more than two levels of priority
• Example: Assume process L, M and H have low, medium and high
priority resp.
• L is currently executing and using resource R
• H becomes runnable and also requests R – it blocks since R is locked, so L
continues
• M becomes runnable and preempts L
• M has been dispatched while H (with a higher priority) has to wait for a
resource being held by L
Priority Inversion - prevention
• Restrict priority levels to 2
• Not practical on most systems
• Priority inheritance
• Lower priority process holding a resource required by higher priority process
temporarily adopts the higher priority
Classical synchronization problems
• Bounded buffer
• Readers-writers
• Dining philosophers
Bounded buffer problem
• Producer-consumer problem using a bounded buffer
• Shared data

int n;
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0
Producer
do {
. . .
/* produce an item in next produced */
. . .
wait(empty);
wait(mutex);
. . .
/* add next produced to the buffer */
. . .
signal(mutex);
signal(full);
} while (true);
Consumer
do {
wait(full);
wait(mutex);
. . .
/* remove an item from buffer to next consumed */
. . .
signal(mutex);
signal(empty);
. . .
/* consume the item in next consumed */
. . .
} while (true);
Readers-writers problem
• First readers-writers problem
• One writer at a time – no readers permitted
• Multiple readers at a time
• Readers must not wait for writer to start writing
• Second readers-writers
• Writer must write as soon as possible – no new reader admitted as soon as
writer is ready to write
• Applications:
• Can distinguish between readers and writers
• More readers than writers
First readers-writers problem – shared data
semaphore rw_mutex = 1;
semaphore mutex = 1;
int read count = 0;
First readers-writers problem - writer

do {
wait(rw_mutex);
. . .
/* writing is performed */
. . .
signal(rw_mutex);
} while (true);
First readers-writers problem - reader
do {
wait(mutex);
read count++;
if (read count == 1)
wait(rw_mutex);
signal(mutex);
. . .
/* reading is performed */
. . .
wait(mutex);
read count--;
if (read count == 0)
signal(rw_mutex);
signal(mutex);
} while (true);
Dining philosophers problem
Dining philosophers problem
do {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
. . .
/* eat for awhile */
. . .
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
. . .
/* think for awhile */
. . .
} while (true);
How to deal with deadlocks
• Allows only 4 philosophers to attempt to pick up a chopstick
• Allow a philosopher to attempt to pick up chopsticks only if both are
free – critical section
• Asymmetric solution
Weakness of semaphores
• Non-adherence to the wait()-signal() sequence by processes
• Deliberate or accidental

signal(mutex);
...
critical section
...
wait(mutex);
• Broken mutual exclusion
Weakness of semaphores
• Non-adherence to the wait()-signal() sequence by processes
• Deliberate or accidental

wait(mutex);
...
critical section
...
wait(mutex);
• Deadlock
monitor monitor_name{
Monitors /* shared variable declarations */

function P1 ( . . . ) {
• Programming . . .
language support for }
synchronization function P2 ( . . . ) {
. . .
• Solves the semaphore }
problems .
.
.
• Available in languages function Pn ( . . . ) {
like Java, C# . . .
}
• Monitor ADT
initialization_code ( . . . ) {
. . .
}
}
Monitors
• condition type
• supports two operations
• [Link]()
• [Link]()
•What happens when process P signals process Q?
• Either signal and wait – P must wait for Q to exit monitor or wait
• Or signal and continue – Q must still wait for P to exit monitor or wait
Dining philosophers solution using monitors
monitor DiningPhilosophers{
enum {THINKING, HUNGRY, EATING} state[5];
condition self[5];

void pickup(int i) {
state[i] = HUNGRY;
test(i);
if (state[i] != EATING)
self[i].wait();
}

void putdown(int i) {
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}
...
}
Dining philosophers solution using monitors
monitor DiningPhilosophers{
...
void test(int i) {
if ((state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING)) {
state[i] = EATING;
self[i].signal();
}
}

initialization_code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
Dining philosophers solution using monitors
• No two neighbors eat at the same time
• No deadlocks
• Starvation may still occur

You might also like