When a lock is not enough
In Module 9, a mutex kept workers from updating a shared counter at the same time. But what if a worker needs data that another worker has not produced yet? Taking a lock protects the data; it does not make the data ready.
We will use a condition variable to wait until shared state allows a worker to proceed. This connects to Chapter 4, §4.8.3, where the book uses condition variables in a barrier. Our producer-and-consumer example isolates the waiting pattern before tackling a whole-group barrier. The complete program is pth_ready.c.
Predict the problem
One worker produces the answer 42; another consumes it. Initially, value = 0 and ready = 0. Both variables are shared.
Discuss: If the consumer locks the mutex first, what value would it read? Would creating the producer first guarantee that the answer is ready? Would a one-second sleep guarantee correctness?
A worker’s creation order does not guarantee its execution order. Sleeping guesses at timing. We need a rule based on the actual state: consume only when ready is true.
Wait for the condition
Create the file with the usual headers. Add:
int value = 0, ready = 0;
pthread_mutex_t lock;
pthread_cond_t available;The consumer function is:
void *consume(void *argument) {
(void)argument;
pthread_mutex_lock(&lock);
while (!ready) {
pthread_cond_wait(&available, &lock);
}
int result = value;
pthread_mutex_unlock(&lock);
printf("Consumer received: %d\n", result);
return NULL;
}Read the wait as three steps:
- Release the mutex and start waiting, as one coordinated operation.
- Wait for a notification; the thread can also wake for other reasons.
- Reacquire the mutex before returning from
pthread_cond_wait.
The worker must hold the mutex before calling wait. Releasing it while waiting lets the producer acquire it and change the shared state. The consumer checks ready again after waking; it proceeds only when the condition is true. This is why we use while, not if. A wake-up is permission to check again, not proof that the data is ready. This pattern follows the POSIX condition-wait specification.
Trace: While the consumer waits, who can hold lock? When wait returns, does the consumer need another lock call?
Produce the value and notify
void *produce(void *argument) {
(void)argument;
int answer = 6 * 7;
pthread_mutex_lock(&lock);
value = answer;
ready = 1;
pthread_cond_signal(&available);
pthread_mutex_unlock(&lock);
return NULL;
}The calculation is local. Publishing value and updating ready use the same mutex as the consumer. The signal notifies a waiter; the consumer can resume through wait only after acquiring the mutex. A condition variable does not hold the answer or copy it to another thread.
If the producer finishes before the consumer starts waiting, the program still works: the consumer sees ready = 1 and skips the wait. The shared flag remembers that the data is ready. A signal by itself is not stored for a future waiter.
Complete main and run
Initialize both synchronization objects before creating workers:
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&available, NULL);Create one consumer and one producer, then join both. Destroy the condition variable and mutex after both joins. Use the complete source to check these calls. As in our earlier examples, we assume successful thread and synchronization calls.
gcc -Wall -pthread -o pth_ready pth_ready.c
./pth_readyExpected output:
Consumer received: 42
Both workers finished
Run several times, then reverse the creation order and rebuild. Both orders should work. Do not add sleeps to arrange the workers.
Trace both possible orders
On paper, trace these cases with columns for producer, consumer, ready, and mutex ownership:
- Consumer acquires the mutex first and waits.
- Producer publishes the answer before the consumer acquires the mutex.
Include every lock, wait, signal, and unlock. Where does the consumer read the answer in each case?
Signal, broadcast, and joining
| Operation | Purpose |
|---|---|
| Mutex | Protect accesses to shared state. |
| Condition wait | Wait for state to become suitable, releasing the mutex while waiting. |
| Condition signal | Notify a waiter. |
| Condition broadcast | Notify all current waiters. |
| Join | Wait for a particular worker to finish. |
pthread_cond_broadcast does not copy data like MPI_Bcast. Threads already share the data; broadcasting wakes waiters so they can reacquire the mutex and check their conditions.
Try: Add a second consumer that reads the same published value. Change the producer’s signal to pthread_cond_broadcast, create and join both consumers, and run it. Each should print 42. Here the value remains available: reading it does not consume or erase it. In a queue where a value can be removed only once, the state and waiting rules would need to account for that.
Check your understanding
- Why does waiting while holding an ordinary mutex, without releasing it, block progress?
- What remembers that the answer is ready if the signal comes early?
- Why is the condition tested in a loop?
- Why can printing happen after unlocking in our consumer?
- The producer needs that mutex to publish the value. Condition wait releases it while waiting.
- The shared
readyflag, accessed under the mutex. - A notification or other wake-up does not establish that the condition is true; recheck it under the lock.
- The consumer copied the value into its own local
result. It no longer needs to access the shared value for printing.