Semaphores
- Review: to avoid race conditions, we require:
* process cannot enter its critical section if
another process is already in its corresponding
critical section (e.g. one that affects the same variable)
* in general, we need a way to make process block until desired
event occurs in other process (e.g. leaves critical section)
- general solution must satisfy at least these two conditions:
- only one process in critical section at a time (mutual exclusion)
- once a process attempts to enter its critical section,
it must eventually be allowed to do so (time bounded)
- semaphore: non-negative integer variable set/tested only by atomic ops:
increment: V(s): [s = s + 1]
test: P(S): [while (s == 0) { wait }; s= s-1]
- these operations cannot be interrupted, except during { wait }
- very similar to enter/exit lock seen earlier but more powerful
- general example of use:
semaphore mutex = 1;
P1: P2:
while (TRUE) { while (TRUE) {
< do some work > < do some work >
P(mutex); P(mutex);
< critical section > < critical section >
V(mutex); V(mutex);
} }
- specific example: shared balance problem
semaphore mutex = 1;
P1: P2:
... ...
P(mutex); P(mutex);
balance += amount; balance -= amount;
V(mutex); V(mutex);
... ...
- problem: solve the following synchronization problem using semaphores
(P2 must wait for P1 to set x before proceeding;
P1 must wait for P2 to set y before proceeding)
shared int x, y;
P1: P2:
while (TRUE) { while (TRUE) {
< compute A1 > /* wait for x */
set x; get x;
< compute A2 > < compute B1 >
/* wait for y */ set y;
get y; < compute B2 >
} }
- what's wrong with the following?
shared int x, y;
semaphore mutex = 1;
P1: P2:
while (TRUE) { while (TRUE) {
< compute A1 > P (mutex);
set x; get x;
V (mutex); < compute B1 >
< compute A2 > set y;
P (mutex); V (mutex);
get y; < compute B2 >
} }
- first, there's nothing stopping P1 from running by itself!
- what happens if P1 gets to V(mutex) before P2 gets to P(mutex)
and this continues? (no synchronization achieved)
- solution: use two semaphores, s1 and s2, and start each at 0
shared int x, y;
semaphore s1 = 0;
semaphore s2 = 0;
P1: P2:
while (TRUE) { while (TRUE) {
< compute A1 > P (s1);
set x; get x;
V (s1); < compute B1 >
< compute A2 > set y;
P (s2); V (s2);
get y; < compute B2 >
} }
- bounded buffer problem:
producer() { consumer() {
buf_type *next, *item; buf_type *next, *item;
while (TRUE) { while (TRUE) {
produce (item); /* wait for full buffer */
next = obtain(fullPool);
/* wait for empty buffer */
next = obtain(emptyPool); copy (next, item);
release (next, emptyPool);
copy(item, next);
release(next, fullPool); consume (item);
} }
} }
- solution requires the introduction of 'counting' semaphores:
semaphore full = 0; /* number of full buffers */
semaphore empty = N; /* number of empty buffers */
- let producer signal creation of full buffer by incrementing full sem
and use of an empty buffer by decrementing empty sem
- let consumer signal creation of empty buffer by incrementing empty sem
and use of a full buffer by decrementing full sem
- also need one more general purpose mutex semaphore for any use of pools
semaphore full = 0; /* number of full buffers */
semaphore empty = N; /* number of empty buffers */
producer() { consumer() {
buf_type *next, *item; buf_type *next, *item;
while (TRUE) { while (TRUE) {
produce (item); /* wait for full buffer */
P(full);
next = obtain(fullPool);
/* wait for empty buffer */
P(empty);
next = obtain(emptyPool); copy (next, item);
release (next, emptyPool);
copy(item, next);
release (next, fullPool); V(empty);
V(full); consume (item);
} }
} }
Dining Philosopher's Problem revisited:
- homework question: will this avoid deadlock?
semaphore hashi[5];
philosopher(int i) {
while (TRUE) {
Ponder(universe);
/* prepare to eat */
P(hashi[i]);
P(hashi[(i+1) mod 5];
Eat();
/* release chopsticks */
V(hashi[(i+1) mod 5];
V(hashi[i]);
}
}
main () {
/* all chopsticks are on table */
for (i = 0; i < 5; i++)
hashi[i] = 1;
for (i = 0; i < 5; i++)
ThreadCreate(philosopher, i); /* assume we can do this */
}