Skip to content

Threads and real time

A SHUFFLE application has three kinds of threads. The cores never create threads; they are given them through ports.

Thread Owned by What runs there
control the backend’s message loop, reached through the TaskQueue port every state change, commands, timers, UI updates, persistence requests
audio (and real-time processing threads) the audio device or the engine noexcept SHUFFLE_RT processing functions, nothing else
workers the WorkerPool port file loading and writing, scans, analysis, network
sequenceDiagram
    participant C as control thread
    participant A as audio thread
    participant W as worker
    C->>W: load a song (WorkerPool)
    W-->>C: done (posted to the TaskQueue)
    C->>A: new program, through a lock-free FIFO or an atomic swap
    A-->>C: levels, positions (atomics), events (FIFO drained on a timer)
    C->>C: old program destroyed here, never on the audio thread

A core asks TaskQueue::isControlThread() and posts work with post(); it never assumes which concrete thread that is. The same core therefore runs under JUCE’s message thread, under your own loop (the miniaudio example runs posted tasks from main), or under a test that calls FakeTaskQueue::runPending() when it wants time to move.

The audio thread must deliver a block before the device needs it, every few milliseconds, on every platform. Anything that can wait (allocation, a lock, I/O, an exception) can miss that deadline. So:

  • functions that may run there are noexcept and marked SHUFFLE_RT;
  • with Clang 20 and SHUFFLE_ENGINE_RT_CHECKS=ON, the compiler rejects a marked function that allocates, locks, throws or calls unmarked code;
  • RealtimeSanitizer checks the same functions while tests run;
  • data reaches the audio thread prepared in advance, through lock-free structures with fixed capacity.

Ports cross the boundary per block or per message, never per sample, so the inner loops stay plain code the compiler can optimise.

The control thread never waits either: opening a device, scanning plugins and loading a song all answer later through a listener or a posted completion. Mobile systems require it, and it keeps a UI responsive on every platform.

How to apply this in your code: Respect the real-time rules. More: architecture.md.