A multithreaded producer-consumer pipeline in C, simulating a real-time frame streaming architecture using POSIX threads.
Two threads run concurrently inside the same process:
- Producer thread — generates fake JPEG frames at ~10fps and writes them to a shared buffer
- Consumer thread — reads frames from the buffer and prints them as they arrive
Synchronisation is handled with a pthread_mutex and pthread_cond — the consumer sleeps until the producer signals that a new frame is ready. Clean shutdown on SIGINT (Ctrl+C) is handled via a signal handler that wakes all sleeping threads before exiting.
My internship involves building an MJPEG streaming pipeline on an ARM camera SoC — camera firmware writes JPEG frames to /tmp, and a CGI process serves them over HTTP. That pipeline is sequential: one write, one read, repeat.
mtstream models what a better architecture looks like — a dedicated capture thread and a dedicated streaming thread running in parallel, sharing frames through a mutex-protected buffer. This is how a production MJPEG server would be structured internally.
┌─────────────────┐ shared buffer ┌─────────────────┐
│ Producer thread │ ──── mutex + cond_signal ──▶ │ Consumer thread │
│ (frame capture) │ │ (frame serving) │
└─────────────────┘ └─────────────────┘
│ │
locks mutex waits on cond
writes frame reads frame
signals consumer resets ready flag
unlocks + sleeps unlocks
makemake clean./mtstreamPress Ctrl+C to stop. Both threads exit cleanly.
Consumer: frame 1 — JPEG_DATA_1
Consumer: frame 2 — JPEG_DATA_2
Consumer: frame 3 — JPEG_DATA_3
...
^CFrame 17: JPEG DATA 17
program safely exited!!
pthread_mutex_lock/pthread_mutex_unlockfor mutual exclusion — protecting shared buffer state from concurrent accesspthread_cond_waitatomically releases the mutex and puts the thread to sleep — it reacquires the mutex before returning when signalledpthread_cond_signalvspthread_cond_broadcast— signal wakes one waiter, broadcast wakes all — broadcast is needed in the signal handler to wake a sleeping consumer on shutdown- Why the inner wait loop must check
&& running— the consumer can wake up from broadcast withready == 0(no new frame, just a shutdown signal) and must not process stale data - Why
usleepgoes outside the mutex lock — sleeping while holding a lock blocks the consumer for the entire sleep duration volatile int running— prevents the compiler from caching the flag in a register and missing the signal handler's update
- POSIX threads (
pthreads) —pthread_create,pthread_join - Mutex synchronisation —
pthread_mutex_t - Condition variables —
pthread_cond_t,pthread_cond_wait,pthread_cond_signal,pthread_cond_broadcast - Signal handling —
SIGINT,signal() - Producer-consumer pattern in C
- Clean thread shutdown without
pthread_cancel