Linux System Programming · advanced · ~14 min

The producer/consumer pattern

Coordinate threads using a bounded queue, a mutex, and condition variables.

Lesson

The pattern

Producer/consumer is the classic multi-threaded design.

  • One or more producer threads add items to a queue.
  • One or more consumer threads remove items from that queue.

The queue is the meeting point where the threads synchronise.

Building blocks

You need four pieces:

  • A buffer to hold the items. This can be a circular buffer (a fixed-size array reused in a loop) or a linked list.
  • A mutex that protects the queue, so only one thread touches it at a time.
  • A condition variable cv_not_empty. Consumers wait on it when the queue is empty. A producer signals it with pthread_cond_signal after adding an item.
  • A condition variable cv_not_full. Producers wait on it when the queue is full. A consumer signals it after removing an item.

A condition variable lets a thread sleep until another thread tells it that something changed.

Always wait in a while loop

Wait on a condition variable inside a while loop that checks the predicate (the condition you are waiting for). Do not use a plain if.

The reason is spurious wakeups: POSIX explicitly allows pthread_cond_wait to return even when no signal was sent. The while loop re-checks the condition and goes back to sleep if it is not yet true.

while (count == 0)
    pthread_cond_wait(&cv_not_empty, &m);

Code examples

#include <pthread.h>

#define CAP 8
static int buf[CAP];
static int head = 0, tail = 0, count = 0;
static pthread_mutex_t m  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  ne = PTHREAD_COND_INITIALIZER;  /* not empty */
static pthread_cond_t  nf = PTHREAD_COND_INITIALIZER;  /* not full */

static void produce(int x) {
    pthread_mutex_lock(&m);
    while (count == CAP) pthread_cond_wait(&nf, &m);
    buf[tail] = x; tail = (tail + 1) % CAP; count++;
    pthread_cond_signal(&ne);
    pthread_mutex_unlock(&m);
}

static int consume(void) {
    pthread_mutex_lock(&m);
    while (count == 0) pthread_cond_wait(&ne, &m);
    int x = buf[head]; head = (head + 1) % CAP; count--;
    pthread_cond_signal(&nf);
    pthread_mutex_unlock(&m);
    return x;
}

Common mistakes

  • Using if instead of while around pthread_cond_wait. A spurious wakeup will then let a consumer dequeue from an empty buffer.
  • Signalling the wrong condition variable. For example, signalling not_empty after consuming an item instead of not_full.

Summary

  • Producer/consumer = bounded queue + mutex + two condition variables (not_empty, not_full).
  • Producers wait while the queue is full; consumers wait while it is empty.
  • Always wait inside a while (predicate-not-yet-true) loop, never a plain if, to handle spurious wakeups.

Practice with these exercises