30.2.3 — Testing a Condition Variable’s Predicate
Why you MUST use while, not if — spurious wakeups explained
What Is a “Predicate”?
A predicate is the condition (involving one or more shared variables) that a thread is waiting for. For example, if a consumer waits for items to be available, the predicate is (avail > 0). The consumer waits while the predicate is false and proceeds when it becomes true.
The critical rule that every beginner must memorize:
while, NEVER use if, when checking a condition variable predicate.The Correct Pattern (while) vs The Wrong Pattern (if)
❌ WRONG — Using if
pthread_mutex_lock(&mtx);
if (avail == 0) /* WRONG! */
pthread_cond_wait(&cond, &mtx);
/* DANGER: avail might still be 0! */
avail--; /* Could be wrong */
pthread_mutex_unlock(&mtx);
✅ CORRECT — Using while
pthread_mutex_lock(&mtx);
while (avail == 0) /* CORRECT */
pthread_cond_wait(&cond, &mtx);
/* Guaranteed: avail > 0 here */
avail--;
pthread_mutex_unlock(&mtx);
Why Must You Use while? Three Reasons
Reason 1: Another thread may grab the work first
Suppose two consumer threads (C1 and C2) are both waiting on avail == 0. The producer adds one item and calls broadcast() — both consumers wake up.
C1 gets the mutex first, finds avail == 1, consumes it, sets avail = 0, unlocks. Now C2 gets the mutex. With an if statement, C2 would proceed directly and try to consume from an empty queue — which is wrong. With a while loop, C2 re-checks: sees avail == 0, and correctly goes back to sleep.
Reason 2: “Loose” predicates
Sometimes it is simpler to signal a condition variable based on an approximation of the predicate’s state. For example, “there might be work” rather than “there definitely is work.” The waiting thread can verify for itself by re-checking the predicate after waking. A while loop handles this naturally.
Reason 3: Spurious wakeups
Spurious wakeup: A thread waiting on a condition variable may wake up even though no other thread called signal() or broadcast(). This is not a bug — it is explicitly permitted by the POSIX standard. It happens rarely and is a consequence of efficient implementation techniques on some multiprocessor hardware.
A while loop handles spurious wakeups correctly — the thread wakes up, re-checks the predicate, finds it false, and goes back to sleep. An if statement would cause the thread to proceed incorrectly with the predicate still false.
Code Example 1: Demonstrating the while-loop Pattern
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0;
/* Two consumers — shows why while is needed when broadcast is used */
void *consumer(void *arg)
{
int id = *((int *) arg);
int consumed = 0;
while (consumed < 3) {
pthread_mutex_lock(&mtx);
/* MUST use while — could be woken by broadcast when avail is 0 */
while (avail == 0) {
printf("Consumer %d: waiting (avail=%d)\n", id, avail);
pthread_cond_wait(&cond, &mtx);
/* After waking, we re-check: maybe the other consumer got it */
}
/* We know avail > 0 HERE — guaranteed by while loop */
avail--;
consumed++;
printf("Consumer %d: consumed item %d (avail=%d)\n",
id, consumed, avail);
pthread_mutex_unlock(&mtx);
}
printf("Consumer %d: finished\n", id);
return NULL;
}
void *producer(void *arg)
{
for (int i = 0; i < 6; i++) {
struct timespec ts = {0, 500000000}; /* 500ms */
nanosleep(&ts, NULL);
pthread_mutex_lock(&mtx);
avail++;
printf("Producer: added item (avail=%d)\n", avail);
pthread_mutex_unlock(&mtx);
/* broadcast wakes BOTH consumers even though only one can consume */
pthread_cond_broadcast(&cond);
}
return NULL;
}
int main(void)
{
pthread_t prod, c1, c2;
int id1 = 1, id2 = 2;
pthread_create(&c1, NULL, consumer, &id1);
pthread_create(&c2, NULL, consumer, &id2);
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(c1, NULL);
pthread_join(c2, NULL);
return 0;
}
/*
* When broadcast wakes both consumers but avail==1:
* - Consumer 1 gets mutex, finds avail=1, consumes, sets avail=0
* - Consumer 2 gets mutex, re-checks (while loop), finds avail=0,
* goes back to sleep — CORRECT!
* - Without while loop, Consumer 2 would try avail-- on avail=0 — BUG!
*/
Code Example 2: Handling Spurious Wakeups
#include <stdio.h>
#include <pthread.h>
#include <time.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int job_count = 0;
void *worker(void *arg)
{
int id = *((int *) arg);
pthread_mutex_lock(&mtx);
/* This while loop handles ALL three wakeup scenarios:
* 1. Normal wakeup — signal/broadcast called and job_count > 0
* 2. Spurious wakeup — job_count is still 0, go back to sleep
* 3. Multiple waiters — another worker got the job first
*/
while (job_count == 0) {
printf("Worker %d: sleeping (job_count=%d)\n", id, job_count);
pthread_cond_wait(&cond, &mtx);
printf("Worker %d: woke up, rechecking (job_count=%d)\n",
id, job_count);
/* Even if this is a spurious wakeup, we just loop and sleep again */
}
/* Guaranteed to be here only when job_count > 0 */
job_count--;
printf("Worker %d: processing job (job_count=%d)\n", id, job_count);
pthread_mutex_unlock(&mtx);
return NULL;
}
void *dispatcher(void *arg)
{
struct timespec ts = {1, 0};
nanosleep(&ts, NULL);
pthread_mutex_lock(&mtx);
job_count = 3; /* Add 3 jobs */
printf("Dispatcher: added %d jobs\n", job_count);
pthread_mutex_unlock(&mtx);
pthread_cond_broadcast(&cond); /* Wake all workers */
return NULL;
}
int main(void)
{
pthread_t w[3], d;
int ids[3] = {1, 2, 3};
for (int i = 0; i < 3; i++)
pthread_create(&w[i], NULL, worker, &ids[i]);
pthread_create(&d, NULL, dispatcher, NULL);
for (int i = 0; i < 3; i++)
pthread_join(w[i], NULL);
pthread_join(d, NULL);
return 0;
}
/*
* The while loop means: even if a spurious wakeup occurs,
* the worker simply goes back to sleep. The code is correct
* in all cases.
*/
Interview Questions — Predicate & Spurious Wakeups
(avail > 0). The thread should check this predicate before proceeding after waking from pthread_cond_wait().if: Thread B skips the wait and tries to consume from empty queue — bug! With while: Thread B rechecks, finds avail=0, and goes back to sleep — correct!