semaphore like an integer, but…
cannot read/write directly
never negative — wait instead
struct Thread {
...
Semaphore finish_semaphore; /* with initial value 0 */
/* value = 0: either thread not finished OR already joined */
/* value = 1: thread finished AND not joined */
};
thread_join(Thread *t) {
t->finish_semaphore.down();
}
/* assume called when thread finishes */
thread_exit(Thread *t) {
t->finish_semaphore.up();
/* tricky part: deallocating struct Thread safely? */
}GetValue() waits for PutValue() to happen, retrieves value, then allows next PutValue().
PutValue() waits for prior GetValue(), places value, then allows next GetValue().
What goes in the blanks?
sem_post(&empty) / sem_wait(&ready)sem_wait(&ready) / sem_post(&empty)sem_post(&ready) / sem_wait(&empty)sem_post(&ready) / sem_post(&empty)sem_wait(&empty) / sem_post(&ready)What do you need to wait for?
what can you count that will be 0 when you need to wait?
use up/down operations to maintain count
sem_init(&full_slots, ..., 0);
sem_init(&empty_slots, ..., BUFFER_CAPACITY);
sem_init(&mutex, ..., 1 /* # thread that can use buffer at once */);
buffer.set_size(BUFFER_CAPACITY);
...
Produce(item) {
sem_wait(&empty_slots);
sem_wait(&mutex);
buffer.enqueue(item);
sem_post(&mutex);
// tell consumers there is more data
sem_post(&full_slots);
}
Consume() {
// wait until queued item, reserve it
sem_wait(&full_slots);
sem_wait(&mutex);
item = buffer.dequeue();
sem_post(&mutex);
// let producer reuse item slot
sem_post(&empty_slots);
return item;
}
sem_init(&full_slots, ..., 0);
sem_init(&empty_slots, ..., BUFFER_CAPACITY);
sem_init(&mutex, ..., 1 /* # thread that can use buffer at once */);
buffer.set_size(BUFFER_CAPACITY);
...
Produce(item) {
sem_wait(&empty_slots);
sem_wait(&mutex);
buffer.enqueue(item);
sem_post(&mutex);
// tell consumers there is more data
sem_post(&full_slots);
}
Consume() {
// wait until queued item, reserve it
sem_wait(&full_slots);
sem_wait(&mutex);
item = buffer.dequeue();
sem_post(&mutex);
// let producer reuse item slot
sem_post(&empty_slots);
return item;
}
full_slots \(\le\) # items on queue
empty_slots \(\le\) # free slots on queue
sem_init(&full_slots, ..., 0);
sem_init(&empty_slots, ..., BUFFER_CAPACITY);
sem_init(&mutex, ..., 1 /* # thread that can use buffer at once */);
buffer.set_size(BUFFER_CAPACITY);
...
Produce(item) {
sem_wait(&empty_slots);
sem_wait(&mutex);
buffer.enqueue(item);
sem_post(&mutex);
// tell consumers there is more data
sem_post(&full_slots);
}
Consume() {
// wait until queued item, reserve it
sem_wait(&full_slots);
sem_wait(&mutex);
item = buffer.dequeue();
sem_post(&mutex);
// let producer reuse item slot
sem_post(&empty_slots);
return item;
}
exercise: when is full_slots value + empty_slots value != queue size?
sem_init(&full_slots, ..., 0);
sem_init(&empty_slots, ..., BUFFER_CAPACITY);
sem_init(&mutex, ..., 1 /* # thread that can use buffer at once */);
buffer.set_size(BUFFER_CAPACITY);
...
Produce(item) {
sem_wait(&empty_slots);
sem_wait(&mutex);
buffer.enqueue(item);
sem_post(&mutex);
// tell consumers there is more data
sem_post(&full_slots);
}
Consume() {
// wait until queued item, reserve it
sem_wait(&full_slots);
sem_wait(&mutex);
item = buffer.dequeue();
sem_post(&mutex);
// let producer reuse item slot
sem_post(&empty_slots);
return item;
}
ProducerReordered() {
// BROKEN: WRONG ORDER
sem_wait(&mutex);
sem_wait(&empty_slots);
...
sem_post(&mutex);
Consumer() {
sem_wait(&full_slots);
// can't finish until
// Producer's sem_post(&mutex):
sem_wait(&mutex);
...
// so this is not reached
sem_post(&full_slots);
sem_init(&full_slots, ..., 0);
sem_init(&empty_slots, ..., BUFFER_CAPACITY);
sem_init(&mutex, ..., 1 /* # thread that can use buffer at once */);
buffer.set_size(BUFFER_CAPACITY);
...
Produce(item) {
sem_wait(&empty_slots);
sem_wait(&mutex);
buffer.enqueue(item);
sem_post(&mutex);
// tell consumers there is more data
sem_post(&full_slots);
}
Consume() {
// wait until queued item, reserve it
sem_wait(&full_slots);
sem_wait(&mutex);
item = buffer.dequeue();
sem_post(&mutex);
// let producer reuse item slot
sem_post(&empty_slots);
return item;
}
producer: wait (down) empty_slots, post (up) full_slots
consumer: wait (down) full_slots, post (up) empty_slots
two producers or consumers?
Anderson/Dahlin complains about semaphores
argument 1: clearer to have separate constructs for
arugment 2: tricky to verify thread calls up exactly once for every down
sem_t semaphore; // initial value 1
Lock() {
sem_wait(&semaphore);
}
Unlock() {
sem_post(&semaphore);
}
sem_t threads_to_wakeup; // initially 0
Wait(Lock lock) {
lock.Unlock();
sem_wait(&threads_to_wakeup);
lock.Lock();
}
Signal() {
sem_post(&threads_to_wakeup);
}
sem_t private_lock; // initially 1
int num_waiters;
sem_t threads_to_wakeup; // initially 0
Wait(Lock lock) {
sem_wait(&private_lock);
++num_waiters;
sem_post(&private_lock);
lock.Unlock();
sem_wait(&threads_to_wakeup);
lock.Lock();
}
Signal() {
sem_wait(&private_lock);
if (num_waiters > 0) {
sem_post(&threads_to_wakeup);
--num_waiters;
}
sem_post(&private_lock);
}
sem_t private_lock; // initially 1
int num_waiters;
sem_t threads_to_wakeup; // initially 0
Wait(Lock lock) {
sem_wait(&private_lock);
++num_waiters;
sem_post(&private_lock);
lock.Unlock();
sem_wait(&threads_to_wakeup);
lock.Lock();
}
Broadcast() {
sem_wait(&private_lock);
while (num_waiters > 0) {
sem_post(&threads_to_wakeup);
--num_waiters;
}
sem_post(&private_lock);
}
pthread_mutex_t lock;
unsigned int count;
/* condition, broadcast when becomes count > 0 */
pthread_cond_t count_is_positive_cv;
void down() {
pthread_mutex_lock(&lock);
while (!(count > 0)) {
pthread_cond_wait(
&count_is_positive_cv,
&lock);
}
count -= 1;
pthread_mutex_unlock(&lock);
}
void up() {
pthread_mutex_lock(&lock);
count += 1;
/* count must now be
positive, and at most
one thread can go per
call to Up() */
pthread_cond_signal(
&count_is_positive_cv
);
pthread_mutex_unlock(&lock);
}
lock to protect shared state
add cond var for each reason we wait
wait using condvar; broadcast/signal when condition changes
binary semaphores — semaphores that are only zero or one
as powerful as normal semaphores
\tiny{ via Hemmendinger, ‘‘Comments on ‘A correect and unrestrictive implementation of general semaphores’ ’’ (1989); Barz, ‘‘Implementing semaphores by binary semaphores’’ (1983)}
// assuming initialValue > 0
BinarySemaphore mutex(1);
int value = initialValue ;
BinarySemaphore gate(1 /* if initialValue >= 1 */);
/* gate = # threads that can Down() now */
void Down() {
gate.Down();
// wait, if needed
mutex.Down();
value -= 1;
if (value > 0) {
gate.Up();
// because next down should finish
// now (but not marked to before)
}
mutex.Up();
}
void Up() {
mutex.Down();
value += 1;
if (value == 1) {
gate.Up();
// because down should finish now
// but could not before
}
mutex.Up();
}
sem_t gate; // 0 = closed; 1 = open
ReleasingThread() {
... // finish what the other thread is waiting for
while (another thread is waiting and can go) {
sem_post(&gate) // allow EXACTLY ONE thread
... // other bookkeeping
}
...
}
WaitingThread() {
... // indicate that we're waiting
sem_wait(&gate) // wait for gate to be open
... // indicate that we're not waiting
}
int num_waiting = 0;
bool finished = false;
sem_t mutex; // initially 1
sem_t gate; // initially 0
void WaitForFinished() {
sem_wait(&mutex);
if (finished) {
sem_post(&mutex);
} else {
num_waiting += 1;
sem_post(&mutex);
sem_wait(&gate);
}
}
void Finish() {
sem_wait(&mutex);
finished = true;
while (num_waiting > 0) {
num_waiting -= 1;
sem_post(&gate);
}
}
bool finished = false;
pthread_mutex_t mutex;
pthread_cond_t cv;
void WaitForFinished() {
pthread_mutex_lock(&mutex);
while (!finished) {
pthread_cond_wait(&cv, &mutex);
}
pthread_mutex_unlock(&mutex);
}
void Finish() {
pthread_mutex_lock(&mutex);
finished = true;
pthread_cond_broadcast(&cv);
pthread_mutex_unlock(&mutex);
}
ThreadSafeQueue<sem_t> waiters;
Wait(Lock lock) {
sem_t private_semaphore;
... /* init semaphore
with count 0 */
waiters.Enqueue(&semaphore);
lock.Unlock();
sem_post(private_semaphore);
lock.Lock();
}
Signal() {
sem_t *next = waiters.DequeueOrNull();
if (next != NULL) {
sem_post(next);
}
}