KEMBAR78
Semaphore in operating systems engineering | PPTX
31. Semaphore
Operating System: Three Easy Pieces
1
Youjip Won
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
Semaphore: Interact with semaphore
 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 }
Semaphore: Interact with semaphore (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 }
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);
Thread Trace: Single Thread 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
Thread Trace: Two Threads Using A Semaphore
Value Thread 0 State Thread 1 State
1 Running Ready
1 call sem_wait() Running Ready
0 sem_wait() retruns Running Ready
0 (crit set: begin) Running Ready
0 Interrupt; Switch → T1 Ready Running
0 Ready call sem_wait() Running
-1 Ready decrement sem Running
-1 Ready (sem < 0)→sleep sleeping
-1 Running Switch → T0 sleeping
-1 (crit sect: end) Running sleeping
-1 call sem_post() Running sleeping
0 increment sem Running sleeping
0 wake(T1) Running Ready
0 sem_post() returns Running Ready
0 Interrupt; Switch → T1 Ready Running
0 Ready sem_wait() retruns Running
0 Ready (crit sect) Running
0 Ready call sem_post() Running
1 Ready sem_post() returns Running 7
Youjip Won
Semaphores As Condition Variables
 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
Thread Trace: Parent Waiting 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
Thread Trace: Parent Waiting 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
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
Is this busy-waiting solution 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
This code is incorrect!
 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!
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 }
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
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.)
A Solution: Adding Mutual 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)
A Solution: Adding Mutual 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)
A Solution: Adding Mutual 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
Finally, A Working Solution
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)
Finally, A Working Solution
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)
Reader-Writer Locks
 Imagine a 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.
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.)
24
Youjip Won
15 rw->readers++;
16 if (rw->readers == 1)
17 sem_wait(&rw->writelock); // first reader acquires
writelock
18 sem_post(&rw->lock);
19 }
20
21 void rwlock_release_readlock(rwlock_t *rw) {
22 sem_wait(&rw->lock);
23 rw->readers--;
24 if (rw->readers == 0)
25 sem_post(&rw->writelock); // last reader releases
writelock
26 sem_post(&rw->lock);
27 }
28
29 void rwlock_acquire_writelock(rwlock_t *rw) {
30 sem_wait(&rw->writelock);
31 }
32
33 void rwlock_release_writelock(rwlock_t *rw) {
34 sem_post(&rw->writelock);
35 }
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
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
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)
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)
A Solution: Breaking The 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 }
 ALGORITHM
 Define the 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
How To Implement Semaphores
 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 …
How To Implement Semaphores (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 }
 Disclaimer: This lecture 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

Semaphore in operating systems engineering

  • 1.
    31. Semaphore Operating System:Three Easy Pieces 1 Youjip Won
  • 2.
    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
  • 7.
    Thread Trace: TwoThreads Using A Semaphore Value Thread 0 State Thread 1 State 1 Running Ready 1 call sem_wait() Running Ready 0 sem_wait() retruns Running Ready 0 (crit set: begin) Running Ready 0 Interrupt; Switch → T1 Ready Running 0 Ready call sem_wait() Running -1 Ready decrement sem Running -1 Ready (sem < 0)→sleep sleeping -1 Running Switch → T0 sleeping -1 (crit sect: end) Running sleeping -1 call sem_post() Running sleeping 0 increment sem Running sleeping 0 wake(T1) Running Ready 0 sem_post() returns Running Ready 0 Interrupt; Switch → T1 Ready Running 0 Ready sem_wait() retruns Running 0 Ready (crit sect) Running 0 Ready call sem_post() Running 1 Ready sem_post() returns Running 7 Youjip Won
  • 8.
    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 …
  • 24.
    A Reader-Writer Locks(Cont.) 24 Youjip Won 15 rw->readers++; 16 if (rw->readers == 1) 17 sem_wait(&rw->writelock); // first reader acquires writelock 18 sem_post(&rw->lock); 19 } 20 21 void rwlock_release_readlock(rwlock_t *rw) { 22 sem_wait(&rw->lock); 23 rw->readers--; 24 if (rw->readers == 0) 25 sem_post(&rw->writelock); // last reader releases writelock 26 sem_post(&rw->lock); 27 } 28 29 void rwlock_acquire_writelock(rwlock_t *rw) { 30 sem_wait(&rw->writelock); 31 } 32 33 void rwlock_release_writelock(rwlock_t *rw) { 34 sem_post(&rw->writelock); 35 }
  • 25.
    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