Semaphore: A definition
A semaphore is a variable whose value indicates the status of a
common resource. Its purpose is to lock the resource being
used. A process which needs the resource will check the semaphore
for determining the status of the resource followed by the decision
f or proceeding.
We can manipulate with two routines; sem_wait() and sem_post().
Initialization
Declare a semaphore s and initialize it to the value 1
The second argument, 0, indicates that the semaphore is shared between
threads in the same process.
2
Youjip Won
1 #include <semaphore.h>
2 sem_t s;
3 sem_init(&s, 0, 1); // initialize s to the value 1
3.
Semaphore: Interact withsemaphore
sem_wait()
If the value of the semaphore was one or higher when called
sem_wait(), return right away.
It will cause the caller to suspend execution waiting for a subsequent
post.
When negative, the value of the semaphore is equal to the number of
waiting threads.
P(S): IF S > 0
THEN S := S - 1
ELSE (wait on S) 3
Youjip Won
1 int sem_wait(sem_t *s) {
2 decrement the value of semaphore s by one
3 wait if value of semaphore s is negative
4 }
4.
Semaphore: Interact withsemaphore (Cont.)
sem_post()
Simply increments the value of the semaphore.
If there is a thread waiting to be woken, wakes one of them up.
V(S): IF (one or more process are waiting on S)
THEN (let one of these processes proceed)
ELSE S := S +1
4
Youjip Won
1 int sem_post(sem_t *s) {
2 increment the value of semaphore s by one
3 if there are one or more threads waiting, wake one
4 }
5.
Binary Semaphores (Locks)
What should X be?
The initial value should be 1.
5
Youjip Won
1 sem_t m;
2 sem_init(&m, 0, X); // initialize semaphore to X; what should X be?
3
4 sem_wait(&m);
5 //critical section here
6 sem_post(&m);
6.
Thread Trace: SingleThread Using A Semaphore
Value of Semaphore Thread 0 Thread 1
1
1 call sema_wait()
0 sem_wait() returns
0 (crit sect)
0 call sem_post()
1 sem_post() returns
6
Youjip Won
Semaphores As ConditionVariables
What should X be?
The value of semaphore should be set to is 0.
8
Youjip Won
1 sem_t s;
2
3 void *
4 child(void *arg) {
5 printf("childn");
6 sem_post(&s); // signal here: child is done
7 return NULL;
8 }
9
10 int
11 main(int argc, char *argv[]) {
12 sem_init(&s, 0, X); // what should X be?
13 printf("parent: beginn");
14 pthread_t c;
15 pthread_create(c, NULL, child, NULL);
16 sem_wait(&s); // wait here for child
17 printf("parent: endn");
18 return 0;
19 }
A Parent Waiting For Its Child
parent: begin
child
parent: end
The execution result
9.
Thread Trace: ParentWaiting For Child (Case 1)
The parent call sem_wait() before the child has called sem_post().
9
Youjip Won
Value Parent State Child State
0 Create(Child) Running (Child exists; is
runnable)
Ready
0 call sem_wait() Running Ready
-1 decrement sem Running Ready
-1 (sem < 0)→sleep sleeping Ready
-1 Switch→Child sleeping child runs Running
-1 sleeping call sem_post() Running
0 sleeping increment sem Running
0 Ready wake(Parent) Running
0 Ready sem_post() returns Running
0 Ready Interrupt; Switch→Parent Ready
0 sem_wait() retruns Running Ready
10.
Thread Trace: ParentWaiting For Child (Case 2)
The child runs to completion before the parent call sem_wait().
10
Youjip Won
Value Parent State Child State
0 Create(Child) Running (Child exists; is runnable) Ready
0 Interrupt; switch→Child Ready child runs Running
0 Ready call sem_post() Running
1 Ready increment sem Running
1 Ready wake(nobody) Running
1 Ready sem_post() returns Running
1 parent runs Running Interrupt; Switch→Parent Ready
1 call sem_wait() Running Ready
0 decrement sem Running Ready
0 (sem<0)→awake Running Ready
0 sem_wait() retruns Running Ready
11.
11
The Producer-Consumer Problem
An example of the pipelined model
One thread produces data items
Another thread consumes them
Use a bounded buffer between the threads
The buffer is a shared resource
Code that manipulates it is a critical section
Must suspend the producer thread if the buffer is full
Must suspend the consumer thread if the buffer is empty
12.
12
Is this busy-waitingsolution correct?
thread producer {
while(1){
// Produce char c
while (count==n) {
no_op
}
buf[Fill] = c
Fill=Fill+ 1 mod MAX
count++
}
}
thread consumer {
while(1){
while (count==0) {
no_op
}
c = buf[Use]
Use = Use + 1 mod MAX
count--
// Consume char
}
}
0
1
2
n-1
…
Global variables:
char buf[n]
int Fill = 0 // place to add
int MAX = 0 // place to get
int count
13.
13
This code isincorrect!
The “count” variable can be corrupted:
Increments or decrements may be lost!
Possible Consequences:
Both threads may sleep forever
Buffer contents may be over-written
What is this problem called? Race Condition
Code that manipulates count must be made into a critical
section and protected using mutual exclusion!
14.
The Producer/Consumer (Bounded-Buffer)Problem
Producer: put() interface
Wait for a buffer to become empty in order to put data into it.
Consumer: get() interface
Wait for a buffer to become filled before using it.
14
Youjip Won
1 int buffer[MAX];
2 int fill = 0;
3 int use = 0;
4
5 void put(int value) {
6 buffer[fill] = value; // line f1
7 fill = (fill + 1) % MAX; // line f2
8 }
9
10 int get() {
11 int tmp = buffer[use]; // line g1
12 use = (use + 1) % MAX; // line g2
13 return tmp;
14 }
15.
The Producer/Consumer (Bounded-Buffer)Problem
15
Youjip Won
1 sem_t empty;
2 sem_t full;
3
4 void *producer(void *arg) {
5 int i;
6 for (i = 0; i < loops; i++) {
7 sem_wait(&empty); // line P1
8 put(i); // line P2
9 sem_post(&full); // line P3
10 }
11 }
12
13 void *consumer(void *arg) {
14 int i, tmp = 0;
15 while (tmp != -1) {
16 sem_wait(&full); // line C1
17 tmp = get(); // line C2
18 sem_post(&empty); // line C3
19 printf("%dn", tmp);
20 }
21 }
22 …
First Attempt: Adding the Full and Empty Conditions
16.
The Producer/Consumer (Bounded-Buffer)Problem
Imagine that MAX is greater than 1 .
If there are multiple producers, race condition can happen at line f1.
It means that the old data there is overwritten.
We’ve forgotten here is mutual exclusion.
The filling of a buffer and incrementing of the index into the buffer is a critical
section.
16
Youjip Won
21 int main(int argc, char *argv[]) {
22 // …
23 sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with…
24 sem_init(&full, 0, 0); // … and 0 are full
25 // …
26 }
First Attempt: Adding the Full and Empty Conditions (Cont.)
17.
A Solution: AddingMutual Exclusion
17
Youjip Won
1 sem_t empty;
2 sem_t full;
3 sem_t mutex;
4
5 void *producer(void *arg) {
6 int i;
7 for (i = 0; i < loops; i++) {
8 sem_wait(&mutex); // line p0 (NEW LINE)
9 sem_wait(&empty); // line p1
10 put(i); // line p2
11 sem_post(&full); // line p3
12 sem_post(&mutex); // line p4 (NEW LINE)
13 }
14 }
15
(Cont.)
Adding Mutual Exclusion (Incorrectly)
18.
A Solution: AddingMutual Exclusion
18
Youjip Won
(Cont.)
16 void *consumer(void *arg) {
17 int i;
18 for (i = 0; i < loops; i++) {
19 sem_wait(&mutex); // line c0 (NEW LINE)
20 sem_wait(&full); // line c1
21 int tmp = get(); // line c2
22 sem_post(&empty); // line c3
23 sem_post(&mutex); // line c4 (NEW LINE)
24 printf("%dn", tmp);
25 }
26 }
Adding Mutual Exclusion (Incorrectly)
19.
A Solution: AddingMutual Exclusion (Cont.)
Imagine two thread: one producer and one consumer.
The consumer acquire the mutex (line c0).
The consumer calls sem_wait() on the full semaphore (line c1).
The consumer is blocked and yield the CPU.
The consumer still holds the mutex!
The producer calls sem_wait() on the binary mutex semaphore (line
p0).
The producer is now stuck waiting too. a classic deadlock.
19
Youjip Won
20.
Finally, A WorkingSolution
20
Youjip Won
1 sem_t empty;
2 sem_t full;
3 sem_t mutex;
4
5 void *producer(void *arg) {
6 int i;
7 for (i = 0; i < loops; i++) {
8 sem_wait(&empty); // line p1
9 sem_wait(&mutex); // line p1.5 (MOVED MUTEX HERE…)
10 put(i); // line p2
11 sem_post(&mutex); // line p2.5 (… AND HERE)
12 sem_post(&full); // line p3
13 }
14 }
15
(Cont.)
Adding Mutual Exclusion (Correctly)
21.
Finally, A WorkingSolution
21
Youjip Won
(Cont.)
16 void *consumer(void *arg) {
17 int i;
18 for (i = 0; i < loops; i++) {
19 sem_wait(&full); // line c1
20 sem_wait(&mutex); // line c1.5 (MOVED MUTEX HERE…)
21 int tmp = get(); // line c2
22 sem_post(&mutex); // line c2.5 (… AND HERE)
23 sem_post(&empty); // line c3
24 printf(“%dn”, tmp);
25 }
26 }
27
28 int main(int argc, char *argv[]) {
29 // …
30 sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with
…
31 sem_init(&full, 0, 0); // ... and 0 are full
32 sem_init(&mutex, 0, 1); // mutex=1 because it is a lock
33 // …
34 }
Adding Mutual Exclusion (Correctly)
22.
Reader-Writer Locks
Imaginea number of concurrent list operations, including inserts
and simple lookups.
insert:
Change the state of the list
A traditional critical section makes sense.
lookup:
Simply read the data structure.
As long as we can guarantee that no insert is on-going, we can allow many
lookups to proceed concurrently.
22
Youjip Won
This special type of lock is known as a reader-write lock.
23.
A Reader-Writer Locks
Only a single writer can acquire the lock.
Once a reader has acquired a read lock,
More readers will be allowed to acquire the read lock too.
A writer will have to wait until all readers are finished.
23
Youjip Won
1 typedef struct _rwlock_t {
2 sem_t lock; // binary semaphore (basic lock)
3 sem_t writelock; // used to allow ONE writer or MANY readers
4 int readers; // count of readers reading in critical section
5 } rwlock_t;
6
7 void rwlock_init(rwlock_t *rw) {
8 rw->readers = 0;
9 sem_init(&rw->lock, 0, 1);
10 sem_init(&rw->writelock, 0, 1);
11 }
12
13 void rwlock_acquire_readlock(rwlock_t *rw) {
14 sem_wait(&rw->lock);
15 …
A Reader-Writer Locks(Cont.)
The reader-writer locks have fairness problem.
It would be relatively easy for reader to starve writer.
How to prevent more readers from entering the lock once a writer is
waiting?
25
Youjip Won
26.
The Dining Philosophers
Assume there are five “philosophers” sitting around a table.
Between each pair of philosophers is a single fork (five total).
The philosophers each have times where they think, and don’t need any forks,
and times where they eat.
In order to eat, a philosopher needs two forks, both the one on their left and the
one on their right.
The contention for these forks.
26
Youjip Won
P1
f1
P0
P4
f0
f4
P3
f3
P2
f2
27.
The Dining Philosophers(Cont.)
Key challenge
There is no deadlock.
No philosopher starves and never gets to eat.
Concurrency is high.
Philosopher p wishes to refer to the fork on their left call left(p).
Philosopher p wishes to refer to the fork on their right call right(p).
27
Youjip Won
while (1) {
think();
getforks();
eat();
putforks();
}
// helper functions
int left(int p) { return p; }
int right(int p) {
return (p + 1) % 5;
}
Basic loop of each philosopher Helper functions (Downey’s solutions)
28.
The Dining Philosophers(Cont.)
We need some semaphore, one for each fork: sem_t forks[5].
Deadlock occur!
If each philosopher happens to grab the fork on their left before any
philosopher can grab the fork on their right.
Each will be stuck holding one fork and waiting for another, forever.
28
Youjip Won
1 void getforks() {
2 sem_wait(forks[left(p)]);
3 sem_wait(forks[right(p)]);
4 }
5
6 void putforks() {
7 sem_post(forks[left(p)]);
8 sem_post(forks[right(p)]);
9 }
The getforks() and putforks() Routines (Broken Solution)
29.
A Solution: BreakingThe Dependency
Change how forks are acquired.
Let’s assume that philosopher 4 acquire the forks in a different order.
There is no situation where each philosopher grabs one fork and is stuck
waiting for another. The cycle of waiting is broken.
29
Youjip Won
1 void getforks() {
2 if (p == 4) {
3 sem_wait(forks[right(p)]);
4 sem_wait(forks[left(p)]);
5 } else {
6 sem_wait(forks[left(p)]);
7 sem_wait(forks[right(p)]);
8 }
9 }
30.
ALGORITHM
Definethe number of philosophers
Declare one thread per philosopher
Declare one semaphore (represent chopsticks) per philosopher
When a philosopher is hungry
See if chopsticks on both sides are free
Acquire both chopsticks or
eat
restore the chopsticks
If chopsticks aren’t free
Wait till they are available
30
Youjip Won
31.
How To ImplementSemaphores
Build our own version of semaphores called Zemaphores
31
Youjip Won
1 typedef struct __Zem_t {
2 int value;
3 pthread_cond_t cond;
4 pthread_mutex_t lock;
5 } Zem_t;
6
7 // only one thread can call this
8 void Zem_init(Zem_t *s, int value) {
9 s->value = value;
10 Cond_init(&s->cond);
11 Mutex_init(&s->lock);
12 }
13
14 void Zem_wait(Zem_t *s) {
15 Mutex_lock(&s->lock);
16 while (s->value <= 0)
17 Cond_wait(&s->cond, &s->lock);
18 s->value--;
19 Mutex_unlock(&s->lock);
20 }
21 …
32.
How To ImplementSemaphores (Cont.)
Zemaphore don’t maintain the invariant that the value of the semaphore.
The value never be lower than zero.
This behavior is easier to implement and matches the current Linux
implementation.
32
Youjip Won
22 void Zem_post(Zem_t *s) {
23 Mutex_lock(&s->lock);
24 s->value++;
25 Cond_signal(&s->cond);
26 Mutex_unlock(&s->lock);
27 }
33.
Disclaimer: Thislecture slide set was initially developed for Operating System course in
Computer Science Dept. at Hanyang University. This lecture slide set is for OSTEP book
written by Remzi and Andrea at University of Wisconsin.
33
Youjip Won