condition variables revisited copyright ©: university of illinois cs 241 staff1
TRANSCRIPT
Condition Variables Revisited
Copyright ©: University of Illinois CS 241 Staff 1
Condition Variable
Without condition variables, Threads continually poll to check if the
condition is met Busy waiting!
With condition variables Same goal without polling
Copyright ©: University of Illinois CS 241 Staff 2
Inside a condition variable
struct pthread_cond {
int waiting;
handle_t semaphore;
};
Copyright ©: University of Illinois CS 241 Staff 3
Number of threads waiting on the condition variable
A semaphore for synchronization
Inside a condition variable
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
atomic_increment(&cond->waiting);
pthread_mutex_unlock(mutex);
if (wait(cond->semaphore, INFINITE) < 0)
return errno;
atomic_decrement(&cond->waiting);
pthread_mutex_lock(mutex);
return 0;
}
Copyright ©: University of Illinois CS 241 Staff 4
pthread_mutex_lock is always called before pthread_cond_wait to acquire lock
Hav
e lo
ck
int pthread_cond_signal(pthread_cond_t *cond) {
if (cond->waiting)
semrel(cond->semaphore, 1);
return 0;
}
Hav
e lo
ck
Thread always has lock when returning from pthread_cond_wait
More Complex Example
Master thread Spawns a number of concurrent slaves Waits until all of the slaves have finished to exit Tracks current number of slaves executing
A mutex is associated with count and a condition variable with the mutex
Copyright ©: University of Illinois CS 241 Staff 5
Example
#include <stdio.h>
#include <pthread.h>
#define NO_OF_PROCS 4
typedef struct _SharedType {
int count; /* number of active slaves */
pthread_mutex_t lock; /* mutex for count */
pthread_cond_t done; /* sig. by finished slave */
} SharedType, *SharedType_ptr;
SharedType_ptr shared_data;
Copyright ©: University of Illinois CS 241 Staff 6
Example: Main
main(int argc, char **argv) {
int res;
/* allocate shared data */
if ((sh_data = (SharedType *) malloc(sizeof(SharedType))) == NULL) {
exit(1);
}
sh_data->count = 0;
/* allocate mutex */
if ((res = pthread_mutex_init(&sh_data->lock, NULL)) != 0) {
exit(1);
}
/* allocate condition var */
if ((res = pthread_cond_init(&sh_data->done, NULL)) != 0) {
exit(1);
}
/* generate number of slaves to create */
srandom(0);
/* create up to 15 slaves */
master((int) random()%16);
}
Copyright ©: University of Illinois CS 241 Staff 7
Example: Main
main(int argc, char **argv) {
int res;
/* allocate shared data */
if ((sh_data = (SharedType *) malloc(sizeof(SharedType))) == NULL) {
exit(1);
}
sh_data->count = 0;
/* allocate mutex */
if ((res = pthread_mutex_init(&sh_data->lock, NULL)) != 0) {
exit(1);
}
/* allocate condition var */
if ((res = pthread_cond_init(&sh_data->done, NULL)) != 0) {
exit(1);
}
/* generate number of slaves to create */
srandom(0);
/* create up to 15 slaves */
master((int) random()%16);
}
Copyright ©: University of Illinois CS 241 Staff 8
pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cont_t data_cond = PTHREAD_COND_INITIALIZER;
Example: Master
master(int nslaves) {
int i;
pthread_t id;
for (i = 1; i <= nslaves; i += 1) {
pthread_mutex_lock(&sh_data->lock);
/* start slave and detach */
shared_data->count += 1;
pthread_create(&id, NULL,
(void* (*)(void *))slave,
(void *)sh_data);
pthread_mutex_unlock(&sh_data->lock);
}
pthread_mutex_lock(&sh_data->lock);
while (sh_data->count != 0)
pthread_cond_wait(&sh_data->done, &sh_data->lock);
pthread_mutex_unlock(&sh_data->lock);
printf("All %d slaves have finished.\n", nslaves);
pthread_exit(0);
}
Copyright ©: University of Illinois CS 241 Staff 9
Example: Slave
void slave(void *shared) {
int i, n;
sh_data = shared;
printf(“Slave.\n", n);
n = random() % 1000;
for (i = 0; i < n; i+= 1)
Sleep(10);
/* mutex for shared data */
pthread_mutex_lock(&sh_data->lock);
/* dec number of slaves */
sh_data->count -= 1;
/* done running */
printf("Slave finished %d cycles.\n", n);
/* signal that you are done working */
pthread_cond_signal(&sh_data->done);
/* release mutex for shared data */
pthread_mutex_unlock(&sh_data->lock);
}
Copyright ©: University of Illinois CS 241 Staff 10
Semaphores vs. CVs
Semaphore Integer value (>=0) Wait does not always
block Signal either releases
thread or inc’s counter If signal releases
thread, both threads continue afterwards
Condition Variables No integer value Wait always blocks
Signal either releases thread or is lost
If signal releases thread, only one of them continue
Copyright ©: University of Illinois CS 241 Staff 11
Classical Synchronization Problems
12Copyright ©: University of Illinois CS 241 Staff
This lecture
Goals Introduce classical synchronization
problems Topics
Producer-Consumer Problem Reader-Writer Problem Dining Philosophers Problem Sleeping Barber’s Problem
13Copyright ©: University of Illinois CS 241 Staff
Chefs cook items and put them on a conveyer belt
14Copyright ©: University of Illinois CS 241 Staff
Waiters pick items off the belt
Now imagine many chefs!
15Copyright ©: University of Illinois CS 241 Staff
And many waiters!
A potential mess!
16Copyright ©: University of Illinois CS 241 Staff
Producer-Consumer Problem
Producers insert items Consumers remove items Shared resource: bounded buffer
Efficient implementation: circular buffer with an insert and a removal pointer
Chef = ProducerWaiter = Consumer
17Copyright ©: University of Illinois CS 241 Staff
Producer-Consumer
18Copyright ©: University of Illinois CS 241 Staff
Chef = ProducerWaiter = Consumer
Producer-Consumer
insertPtr
removePtr
19Copyright ©: University of Illinois CS 241 Staff
Chef = ProducerWaiter = Consumer
What does the chef do with a
new pizza?
Where does the waiter take a pizza from?
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
20Copyright ©: University of Illinois CS 241 Staff
Insert pizza
insertPtr
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
21Copyright ©: University of Illinois CS 241 Staff
Insert pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
22Copyright ©: University of Illinois CS 241 Staff
Insert pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
23Copyright ©: University of Illinois CS 241 Staff
Remove pizza
removePtr
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
24Copyright ©: University of Illinois CS 241 Staff
Insert pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
25Copyright ©: University of Illinois CS 241 Staff
Insert pizza
Producer-Consumer
insertPtr
removePtr
BUFFER FULL: Producer must be blocked!
Chef = ProducerWaiter = Consumer
26Copyright ©: University of Illinois CS 241 Staff
Insert pizza
Producer-Consumer
insertPtrremovePtr
Chef = ProducerWaiter = Consumer
27Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
28Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
29Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
30Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
31Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
32Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
Chef = ProducerWaiter = Consumer
33Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer
insertPtr
removePtr
BUFFER EMPTY: Consumer must be blocked!
Chef = ProducerWaiter = Consumer
34Copyright ©: University of Illinois CS 241 Staff
Remove pizza
Producer-Consumer Summary
Producer Insert items Update insertion pointer
Consumer Execute destructive read on the buffer Update removal pointer
Both Update information about how full/empty the buffer is
Solution Must allow multiple producers and consumers
Copyright ©: University of Illinois CS 241 Staff 35
Challenges
Prevent buffer overflow Prevent buffer underflow Mutual exclusion when modifying the
buffer data structure
36Copyright ©: University of Illinois CS 241 Staff
Solutions
Prevent buffer overflow Block producer when full Counting semaphore to count #free slots 0 block producer
Prevent buffer underflow Mutual exclusion when modifying the
buffer data structure
37Copyright ©: University of Illinois CS 241 Staff
Solutions
Prevent buffer overflow Block producer when full Counting semaphore to count #free slots 0 block producer
Prevent buffer underflow Block consumer when empty Counting semaphore to count #items in buffer 0 block consumer
Mutual exclusion when modifying the buffer data structure
38Copyright ©: University of Illinois CS 241 Staff
Solutions
Prevent buffer overflow Block producer when full Counting semaphore to count #free slots 0 block producer
Prevent buffer underflow Block consumer when empty Counting semaphore to count #items in buffer 0 block consumer
Mutual exclusion when modifying the buffer data structure Mutex protects shared buffer & pointers
39Copyright ©: University of Illinois CS 241 Staff
Assembling the solution
Producer sem_wait(slots), sem_signal(slots) Initialize slots to N
Consumer sem_wait(items), sem_signal(items) Initialize semaphore items to 0
Synchronization mutex_lock(m), mutex_unlock(m)
Buffer management insertptr = (insertptr+1) % N removalptr = (removalptr+1) % N
Copyright ©: University of Illinois CS 241 Staff 40
Producer-Consumer Code
buffer[ insertPtr ] = data;
insertPtr = (insertPtr + 1) % N;
result = buffer[removePtr];
removePtr = (removePtr +1) % N;
Copyright ©: University of Illinois CS 241 Staff 41
Critical Section: move insert pointer
Critical Section: move remove pointer
Producer-Consumer Code
sem_wait(slots);
mutex_lock(mutex);
buffer[ insertPtr ] = data;
insertPtr = (insertPtr + 1) % N;
mutex_unlock(mutex);
sem_signal(items);
sem_wait(items);
mutex_lock(mutex);
result = buffer[removePtr];
removePtr = (removePtr +1) % N;
mutex_unlock(mutex);
sem_signal(slots);
Copyright ©: University of Illinois CS 241 Staff 42
Block if there are no free slots
Block if there
are no items
to take
Counting semaphore – check and decrement the number of free slots
Counting semaphore – check and decrement the number of available items
Done – increment the number of available items
Done – increment the number of free slots
Shared Resource
Readers-Writers Problem
Shared Resource
Readers-Writers Problem
Shared Resource
Readers-Writers Problem
II. Reader-Writer Problem
Readers read data Writers write data Rules
Multiple readers may read the data simultaneously Only one writer can write the data at any time A reader and a writer cannot access data simultaneously
Locking table Whether any two can be in the critical section
simultaneously
Copyright ©: University of Illinois CS 241 Staff 48
Reader Writer
Reader OK No
Writer No No
reader() { while(TRUE) { <other stuff>; sem_wait(mutex); readCount++;
if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex);
/* Critical section */ access(resource);
sem_wait(mutex); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_post(mutex); }}
writer() { while(TRUE) { <other computing>; sem_wait(writeBlock); /* Critical section */ access(resource); sem_signal(writeBlock); }}
Reader-Writer: First Solutionint readCount = 0;semaphore mutex = 1;semaphore writeBlock = 1;
reader() { while(TRUE) { <other stuff>; sem_wait(mutex); readCount++;
if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex);
/* Critical section */ access(resource);
sem_wait(mutex); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_post(mutex); }}
writer() { while(TRUE) { <other computing>; sem_wait(writeBlock); /* Critical section */ access(resource); sem_signal(writeBlock); }}
First reader competes with writersLast reader signals writers
Reader-Writer: First Solutionint readCount = 0;semaphore mutex = 1;semaphore writeBlock = 1;
First reader blocks writers
Last reader unblocks writers
Writer blocks all readers
reader() { while(TRUE) { <other stuff>; sem_wait(mutex); readCount++;
if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex);
/* Critical section */ access(resource);
sem_wait(mutex); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_post(mutex); }}
writer() { while(TRUE) { <other computing>; sem_wait(writeBlock); /* Critical section */ access(resource); sem_signal(writeBlock); }}
Any writer must wait for all readers→ Readers can starve writers→ Updates can be delayed forever→ Not desirable!
Reader-Writer: First Solutionint readCount = 0;semaphore mutex = 1;semaphore writeBlock = 1;
First reader blocks writers
Last reader unblocks writers
reader() { while(TRUE) { <other computing>; sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock);
access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Second Solution
int readCount=0, writeCount=0;semaphore mutex1=1, mutex2=1;Semaphore readBlock=1,writeBlock=1
reader() { while(TRUE) { <other computing>; sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock);
access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Second Solution
int readCount=0, writeCount=0;semaphore mutex1=1, mutex2=1;Semaphore readBlock=1,writeBlock=1
Wait for writers First writer
blocks readers
Last writer unblocks
readers
reader() { while(TRUE) { <other computing>; sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock);
access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Second Solution
int readCount=0, writeCount=0;semaphore mutex1=1, mutex2=1;Semaphore readBlock=1,writeBlock=1
Writers can starve readers→ Reads can be delayed forever→ Not desirable, either!
Better R-W solution idea
Idea: serve requests in order Once a writer requests access, any
entering readers have to block until the writer is done
Advantage? Disadvantage?
55Copyright ©: University of Illinois CS 241 Staff
reader() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock); sem_signal(writePending); access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_signal(writePending); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Fairer Solution?
int readCount = 0, writeCount = 0;semaphore mutex1 = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;
reader() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock); sem_signal(writePending); access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_signal(writePending); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Fairer Solution?
int readCount = 0, writeCount = 0;semaphore mutex1 = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;
Wait for a pending writers that arrived before me
Writer blocks
subsequent readers
4131reader() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock); sem_signal(writePending); access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_signal(writePending); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Fairer Solution?
int readCount = 0, writeCount = 0;semaphore mutex1 = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;
43
1
reader() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock); sem_signal(writePending); access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_signal(writePending); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Fairer Solution?
int readCount = 0, writeCount = 0;semaphore mutex1 = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;
1
reader() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(readBlock); sem_wait(mutex1); readCount++; if(readCount == 1) sem_wait(writeBlock); sem_signal(mutex1); sem_signal(readBlock); sem_signal(writePending); access(resource); sem_wait(mutex1); readCount--; if(readCount == 0) sem_signal(writeBlock); sem_signal(mutex1); }}
writer() { while(TRUE) { <other computing>; sem_wait(writePending); sem_wait(mutex2); writeCount++; if(writeCount == 1) sem_wait(readBlock); sem_signal(mutex2); sem_wait(writeBlock); access(resource); sem_signal(writeBlock); sem_signal(writePending); sem_wait(mutex2); writeCount--; if(writeCount == 0) sem_signal(readBlock); sem_signal(mutex2); }}
Reader-Writer: Fairer Solution?
int readCount = 0, writeCount = 0;semaphore mutex1 = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;
When a reader arrives and a writer is waiting, the reader is suspended behind the writer→ Less concurrency→ Lower performance
Summary
Classic synchronization problems Producer-Consumer Problem Reader-Writer Problem
Saved for next time: Sleeping Barber’s Problem Dining Philosophers Problem
61Copyright ©: University of Illinois CS 241 Staff
Dining Philosophers
N philosophers and N forks Philosophers eat/think Eating needs 2 forks Pick one fork at a time
2
62Copyright ©: University of Illinois CS 241 Staff