QUESTION
Does one pthread_cond_wait correspond exactly to one signal?
A condition variable does not store an event count. It tells waiters that a shared predicate may have become true. A waiter holds the mutex while checking the predicate in a while loop and lets pthread_cond_wait atomically release the mutex and prepare to sleep.
Sending a signal first does not leave a stored credit, and another thread may consume the predicate after wakeup. The waiter therefore checks again with while, not if.
STRUCTURE
Structure diagram
A condition variable does not store events. The queue predicate lives under the mutex; after waking, a waiter reacquires the mutex and checks the predicate.
CALL PATH
Call path
The value of the predicate protected by the mutex is authoritative, not condition-variable state by itself. The queue structure determines signal timing and work-item lifetime.
This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.
SOURCE COORDINATES
Linux 6.18.37 LTS source locations
Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.
| File | Function / structure | What to inspect |
|---|---|---|
| kernel/futex/waitwake.c | futex_wait(), futex_wake() | Sleep/wakeup foundation for pthread condition/mutex contention |
| kernel/futex/core.c | get_futex_key() | Convert a private/shared futex word into a wait-queue key |
| kernel/futex/pi.c | futex_lock_pi() | Kernel path for a priority-inheritance mutex |
COMPLETE PROGRAM
Complete runnable example
The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.
cc -std=c17 -Wall -Wextra -O2 -pthread cond_queue.c -o cond_queue01#include <pthread.h>
02#include <stdio.h>
03
04struct queue {
05 pthread_mutex_t lock;
06 pthread_cond_t ready;
07 int value;
08 int has_value;
09 int stopped;
10};
11
12static void *consumer(void *argument)
13{
14 struct queue *queue = argument;
15 pthread_mutex_lock(&queue->lock);
16 while (!queue->has_value && !queue->stopped)
17 pthread_cond_wait(&queue->ready, &queue->lock);
18 if (queue->has_value) {
19 printf("value=%d\n", queue->value);
20 queue->has_value = 0;
21 }
22 pthread_mutex_unlock(&queue->lock);
23 return NULL;
24}
25
26int main(void)
27{
28 struct queue queue = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, 0, 0 };
29 pthread_t thread;
30 pthread_create(&thread, NULL, consumer, &queue);
31 pthread_mutex_lock(&queue.lock);
32 queue.value = 73;
33 queue.has_value = 1;
34 pthread_cond_signal(&queue.ready);
35 pthread_mutex_unlock(&queue.lock);
36 pthread_join(thread, NULL);
37 pthread_cond_destroy(&queue.ready);
38 pthread_mutex_destroy(&queue.lock);
39 return 0;
40}
CODE NOTES
Code notes
while (!queue->has_valueRechecks whether the predicate is true after a spurious wakeup or preemption by another consumer. The stop predicate is protected by the same lock.
pthread_cond_waitReleases the queue lock, registers and sleeps as a waiter, then reacquires the mutex before returning.
queue.value = 73Updates payload and has_value in the same mutex critical section so a consumer sees them consistently.
pthread_cond_signalCan wake at least one waiter, but stores no event when no waiter exists. Because the predicate is already true, a later waiter does not sleep.
pthread_cond_destroyDestroys the synchronization object only after join guarantees that all waiters are gone.
DETAILS
Detailed behavior
A lost wakeup occurs in the window between unlock and wait
If code checks the predicate, manually unlocks the mutex, and calls a separate sleep primitive, a producer can signal in between and the event is lost. cond_wait makes these two operations atomic at the protocol level.
The producer also changes the predicate under the same mutex, serializing it with the waiter's check.
Choosing signal or broadcast
When one waiter consumes one item, signal is appropriate. When every waiter's predicate can change, as with a global configuration change or shutdown, broadcast is required.
Reduce thundering-herd cost after broadcast through predicates and queue sharding.
Specify the timeout clock
Depending on the implementation, pthread_cond_timedwait's default absolute timeout has CLOCK_REALTIME behavior. Set CLOCK_MONOTONIC in the condition attribute to avoid wall-clock adjustments.
The mutex is held again after a timeout return, so perform one final predicate check before unlocking.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
predicate | Updated under the mutex whenever queue state changes and read by consumers under the same lock | has_value, stopped, count |
pthread_mutex_t | Exists from init through destroy after every user has joined | owner, contention, robust/PI attr |
pthread_cond_t | Used from waiter registration through signal/broadcast and destroyed after waiters disappear | clock, waiter sequence |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| Occasionally waits forever | A signal outside the predicate protocol or an unlock-wait race | Check mutex ownership for every state access |
| An empty queue is consumed | if was used, or a spurious wakeup occurred | while predicate loop |
| shutdown hang | Only some waiters are signaled | stopped predicate + broadcast |
LAB
Verify it yourself
- Create several consumers and compare the number awakened with the number that actually consumes work under signal and broadcast.
- Change while to if, repeatedly issue unnecessary broadcasts to stress progress without a predicate, then restore it.
- Add a CLOCK_MONOTONIC condition attribute and timedwait to implement a timeout independent of wall-clock changes.
./cond_queuestrace -f -e trace=futex,clone,exit ./cond_queuePRIMARY REFERENCES