08.2 Synchronization Patterns C
08.2 Synchronization Patterns C
Patterns
Condition
Variables
&
Semaphores
●
Can we do something more powerful than just locking?
– Condition variables to “signal” other threads.
– Semaphores to count how many things are available.
●
Can we allow multiple readers but only one writer?
●
What can we solve with synchronization?
– How do dining philosophers help us with sychronization?
– What’s a circular buffer?
25-03-03 2
Condition Variables
25-03-03 3
Producer-Consumer pattern -
●
Producer-Consumer
A common programming pattern.
– Producer(s): one set of threads creating data.
- 3 common
lication
– Consumer(s): one set of threads using the data.
-
– Store data: shared resource (e.g., variable or buffer) to hold
- -
the values that have been produced but not yet consumed.
-
-
.. needs protection
This shard resource
25-03-03 4
ABCD: Data race
the variable informa
>
-
shared global ,
static
mases
linkage
static int avail = 0; outside
variabl which means nobody of this fil can
access it
b) No, one increments, the other decrements. Still It's a Race case
rais
mute , in both to
↓ to stor data , avoids
data
serialize acces
rec
25-03-03 6
int main() {
pthread_t t1; static void *thread_func(void *arg) {
pthread_create(&t1, NULL, thread_func, NULL); for (;;) {
pthread_mutex_lock(&mtx);
for (;;) { {
pthread_mutex_lock(&mtx); avail++;
{ }
while (avail > 0) { pthread_mutex_unlock(&mtx);
// Simulate "consume everything available" sleep(1);
printf("I just consumed %d\n", avail); }
avail--;
return 0;
} }
}
}
pthread_mutex_unlock(&mtx); ●
What is the major source of
pthread_join(t1, NULL); inefficiency in this program?
}
↑
a) Wasted space: Use of an int when a bool would be better for `avail`.
I
b) Wasted CPU: main keeps looping even when nothing to consume.
Y
c) Wasted CPU: main locking & unlocking mutex when there are
multiple values to consume. we don't lock again & again
25-03-03 d) Wasted CPU: Program will never end. True Out one
7
but not
thing
a bad
work hers
might lot
bring like
a consumer
but when we
the Good can do 2 things
only
#include <pthread.h>
#include <stdio.h>
T = 0 seconds (Program starts)
#include <unistd.h>
1main() runs and creates t1 (producer thread).
2avail = 0.
static int avail = 0;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; T = 0s to 1s (First loop)
Producer thread (thread_func):
// Producer thread • Locks mutex.
void *thread_func(void *arg) { • Increments avail = 0 + 1 = 1.
• Unlocks mutex.
for (;;) { • Sleeps for 1 second (sleep(1)).
pthread_mutex_lock(&mtx); Consumer thread (main loop):
avail++; • Runs much faster (busy-loop).
pthread_mutex_unlock(&mtx); • Locks mutex.
• Sees avail == 0, so unlocks and repeats the loop.
sleep(1); • Keeps spinning, acquiring/releasing the mutex very quickly (until producer sets avail
} = 1).
return NULL;
}
The consumer thread is wasting CPU by busy-waiting:
// Consumer thread (main) • Even when there is nothing to consume (avail == 0), it keeps spinning inside the for (;;) loop.
int main() { • This is why the earlier quiz said "wasted CPU".
pthread_t t1;
pthread_create(&t1, NULL, thread_func, NULL);
If we remov
sleep (1) from p
for (;;) { avail grows rapidly:
printf("I just consumed %d\n", avail); The consumer is slower because it:
• Prints text to the terminal (which takes time).
avail--; • Runs fewer iterations compared to the blazing-fast producer.
}
pthread_mutex_unlock(&mtx);
// Optional sleep to reduce CPU usage
Y output
-
,
}
I just consumed 1
pthread_join(t1, NULL); X neur I just consumed 1
return 0;
}
expected I just consumed 1
i
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int avail = 0; Producer-Consumer
int main() {
pthread_t t1;
(with Error Checking)
int s = pthread_create(&t1, NULL, thread_func, NULL); =>
if (s != 0) {
perror("pthread_create");
exit(1);
}
for (;;) {
s = pthread_mutex_lock(&mtx);
if (s != 0) {
perror("pthread_mutex_lock");
exit(1);
}
static void *thread_func(void *arg) {
while (avail > 0) { for (;;) {
printf("I just consumed %d\n", avail); int s = pthread_mutex_lock(&mtx);
avail--; if (s != 0) {
} perror("pthread_mutex_lock");
pthread_exit((void *)1);
s = pthread_mutex_unlock(&mtx); }
if (s != 0) { avail++;
perror("pthread_mutex_unlock");
exit(1); s = pthread_mutex_unlock(&mtx);
} if (s != 0) {
} perror("pthread_mutex_unlock");
pthread_exit((void *)1);
s = pthread_join(t1, NULL); }
if (s != 0) { sleep(1);
perror("pthread_create"); }
exit(1);
} return 0;
} }
25-03-03 8
Condition Variable
●
Condition variable purpose:
.. to signal a change in state
●
Using a condition variable:
-
(i) one thread sends a notification to the condition variable,
-
(ii) another thread waits until
a notification is sent to the condition variable.
While waiting,.. sleeps No IPV
usage)
– the thread
25-03-03 9
Integrates with Mutex
●
We want to ensure that consumer(s) are thread safe.
– .. the processing of value to
Let
a occus in a miles
●
A condition variable works closely with a mutex:
We need to hold the mutex We'll wait until there is data available,..
the mutes while
while processing data.. but not hous
waiting
so we don't corrupt
the That way the producer
shared resource (or other consumers)
can do work while we sleep.
25-03-03 10
pthread Condition Variables
●
Define the variable pass pointer
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; hays to
●
Wait on a condition variable >
- it
- pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
– Internally, it will: ↓
●
.. Atomically release the mutes & writ for
condition
to les
released
●
Once signalled,.. wakes up a grabs the mutes.
– Why release mutex when waiting?
.. While we don't stop because
other
holding lock
a
●
Wake up all threads waiting on cond
pthread_cond_broadcast(pthread_cond_t *cond);
-
– All threads wake up and try to grab mutex;
-
.. lock
the
they compete for
25-03-03 12
pthread Condition Variables (cont)
●
Guideline on Signalling
signal() and broadcast() are similar; how to choose?
– If any of the waiting threads is sufficient to process the event:
.. use biwed-coud-signal I useful when
threads
●
It’s likely that all the threads do the same thing.
-
c
●
It’s likely each thread does something different
in response to the event; all need to happen
25-03-03 13
Usage Pattern
Producer: Consumer: While (NU) - ? Why -
on
<do some work producing an item> while ( <no work to do> ) { oreuning
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex); }
●
Details
– .. A condition variable must always use the same
mutes (
– Producer should signal after releasing mutex to avoid waking up a
consumer with cond only to wait for mutex (extra context switch)
– Some systems optimize with "wait morphing" to just move process
from one wait queue to another in the OS
25-03-03 14
Producer-Consumer with Condition Variable
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_unlock(&mtx);
}
pthread_join(t1, NULL);
}
25-03-03 15
Discussion of Code
●
Use of Condition Variables Discussion
– mutex still protects the shared variable avail.
– After producing an item, producer sends a signal to cond to
wake up a waiting thread, if any: pthread_cond_signal(&cond)
●
This notifies other thread there is something to consume.
– At each iteration, consumer checks if there is any available
item to consume (the new while loop).
●
If nothing's available (avail == 0), it sleeps:
pthread_cond_wait()
●
This releases the mutex before sleeping
– Consumer wakes up when signalled by the producer:
●
pthread_cond_wait() grabs mutex before returning.
25-03-03 16
pthread_cond_wait() in loop?
●
Why put pthread_cond_wait() in a loop?
– Consumer only has work to do when: (avail != 0)
(avail != 0) is called the..
– Consumer only waits if there is no data to process.
For this, just if (avial == 0) seems fine.
– But, we must recheck the int main() {
predicate after we are signalled: for (;;) {
pthread_mutex_lock(&mtx);
●
We were waiting on the // This while loop is new.
mutex as well as cond, while (avail == 0) {
pthread_cond_wait(&cond, &mtx);
.. }
25-03-03 17
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0;
Producer-Consumer
int main() {
pthread_t t1;
with Condition Variable
void *res;
int s; with Error Checking
s = pthread_create(&t1, NULL, thread_func, NULL);
if (s != 0) {
perror("pthread_create");
exit(1);
}
int main() {
int s = pthread_mutex_lock(&mtx);
if (s != 0) {
perror("pthread_mutex_lock");
exit(1);
}
s = pthread_mutex_unlock(&mtx);
if (s != 0) {
perror("pthread_mutex_lock");
exit(1);
}
}
25-03-03 19
Semaphores
25-03-03 20
Semaphores
●
.. A semaphore is a clock with a count
– A lock (mutex) is either available or not available, i.e., binary.
– A semaphore is more flexible:
.. It indicates the availability as a count
,
i.e., how many are available.
●
Useful when availability is not binary but a count
-
25-03-03 21
pthread Semaphore Functions
●
Create & Initialize the semaphore
- sem_t sem;
~ sem_init(sem_t *sem, int pshared, unsigned int value);
– Sets current # available to value for sem. -
-
25-03-03 22
pthread Semaphore Functions
●
Wait to "acquire" one of the semaphore's count
sem_wait(sem_t *sem);
– If count is 0, it blocks until count > 0.
-
– When count -
is > 0 it decrements count and returns.
– -
>
Does not guarantee mutual exclusion to a critical section:
.. It availability of
counts the the resource
●
Signal to count-up the semaphore:
sem_post(sem_t *sem);
– If synchronizing access a.. limited resource
then posting can be like.. releasing a resource
●
E.g., allow at most 50 students registered in a course.
– If synchronizing between different sections of code,
then it might indicate a new resource produced.
25-03-03 23
ABCD: Semaphore
●
Which of these creates a semaphore which
behaves the same as a mutex?
a) sem_init(&sem, 0, 0);
X
b) sem_init(&sem, 0, 1);
c) sem_init(&sem, 0, 2);
d) sem_init(&mutex, 0, 10);
25-03-03 24
Semaphore Use Ideas
●
Places to use a Semaphore
Can have a.. single section of code wait & then
–
post
to acquire and release the mutex.
– Can have different parts of the code use them, such as:
doing ●
May still need a mutex to protect shared data.
This
25-03-03 25
Read-Write Lock
25-03-03 26
Read-Write Lock
●
Read-write lock
– Another synchronization primitive.
.. Allows rither unlimited readers or
– a
single
write
●
Multiple readers can all read at the same time!
-
●
Nobody else can access data while anyone writes.
-
●
Acquire lock for reading
-
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
– Allows any thread(s) to grab rwlock for reading as long as
-
there is no thread that hold it for writing.
-
●
Acquire lock for writing
-
pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
-
25-03-03 27
Dining Philosophers
25-03-03 28
Dining Philosophers
●
Problem Description
– Philosophers sit at a round table.
– Philosophers alternate between eating and thinking.
– To eat, a philosopher needs two forks (at their left and right).
To think, no forks are needed.
– One fork between adjacent philosophers.
●
.. run fork is a
resources
25-03-03 30
Try 1: Lock each fork
●
Try 2: One lock per fork. &
●
Let’s create a bad “solution”:
– Have all threads grab their right fork and then their left fork.
-
– But if every philosopher grabs their right fork at the same time,
then.. no philosophers an grab their left forh
– The result:.. deadlock du to hold I wait &
circular
●
Recall: deadlock conditions discussed previously wait
– We can break any of these conditions to avoid a deadlock.
1) Hold-and-wait
2) Circular wait
3) Mutual exclusion
4) No preemption
25-03-03 31
Possible Solutions
●
Solution 1:
.. Havephilospher forks in deff order
a a
– E.g., Most philosophers grab right fork then left fork. Have
have one philosopher grab left fork then right fork.
– .. This orvabs circular-wait condition from
●
Solution 2: curring
.. Try grabbing both locks at once
– Grab the left lock. Try the right lock. If you can't grab it,
..
give up the left lock & try again
– .. This breaks hold
I wait
condition
since no philosopher can hold a fork and wait.
– This does not prevent starvation
-
25-03-03 32
Dining Philosophers Implementation
#define NUMBER 5
pthread_mutex_unlock(&mtx[left]);
pthread_mutex_unlock(&mtx[right]);
}
return 0;
}
25-03-03 33
Bounded Buffer
(Circular Buffer)
25-03-03 34
Bounded Buffer
●
Problem Description
– Multiple threads share a buffer.
– Producer threads place items into the buffer.
●
They must wait.. If unfor is full
– Consumers threads take items from the buffer.
They must wait.. If buffer is not
full/empty
●
●
Details
– Producers:
place items from index 0 to higher indices, one at a time.
– Consumers:
remove items from index 0 to higher indices, one at a time.
– When get to last element,.. wrap-around to indess O
25-03-03 35
Solution
●
Possible solution:
.. Mutex + condition variable
– Mutex protects the data structure for all threads
– Condition variable signals consumer (and producer?)
– Inefficient because..
all threads need to compete
I huck
for
availability
.
25-03-03 36
#define SIZE 10
Semaphores:
static
static
char buf[SIZE] = {0};
int in = 0, out = 0; Elegant Solution
static sem_t filled_cnt;
static sem_t avail_cnt;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int main() {
pthread_t t1;
sem_init(&filled_cnt, 0, 0);
sem_init(&avail_cnt, 0, SIZE);
●
Semaphore
– Synchronization with a count
●
Read-Write Lock
– Multiple readers allowed; only one writer.
●
Classing problems
– Dining Philosophers: worry about deadlock / livelock
25-03-03 38
Summary
●
Condition Variable
– One thread signals another for an event.
●
Semaphore
– Synchronization with a count
●
Read-Write Lock
– Multiple readers allowed; only one writer.
●
Classing problems
– Dining Philosophers: worry about deadlock / livelock
25-03-03 38
Summary
●
Condition Variable
– One thread signals another for an event.
●
Semaphore
– Synchronization with a count
●
Read-Write Lock
– Multiple readers allowed; only one writer.
●
Classing problems
– Dining Philosophers: worry about deadlock / livelock
25-03-03 38
Summary
●
Condition Variable
– One thread signals another for an event.
●
Semaphore
– Synchronization with a count
●
Read-Write Lock
– Multiple readers allowed; only one writer.
●
Classing problems
– Dining Philosophers: worry about deadlock / livelock
25-03-03 38