Also known as: multi-threaded DSP, parallel DSP, concurrent DSP
Multithreaded DSP is the use of multiple CPU threads to process a signal stream in parallel, so that a chain running faster than one core can sustain is split across several cores.1 As sample rates and channel counts climb, a single-threaded SDR pipeline eventually saturates one core; multithreading is how software radio scales onto the many cores of a modern processor while still meeting the real-time deadline of consuming samples as fast as the radio produces them.
How it works
There are two fundamental ways to split DSP work across threads, and real systems combine them.
- Pipeline parallelism. Assign each processing stage — filter, down-convert, demodulate, decode — to its own thread. While the decode thread works on block n, the demod thread is already on block n+1 and the filter on n+2, so all cores stay busy on different parts of the stream at once. Throughput rises to roughly the slowest stage’s rate, and stages communicate through queues (ring buffers). The limits are the bottleneck stage and the cost of hand-offs.
- Data parallelism. Take one expensive stage and run it on several independent blocks concurrently across a worker pool — block 0 to worker A, block 1 to worker B, and so on. This scales past the pipeline’s slowest-stage limit for work that has no cross-block state, but it requires reordering: outputs finish out of sequence and must be reassembled into the original stream order.
Both forms live or die on synchronization. Threads must exchange data without data races, which means lock-free queues or careful mutexes, and every lock or cache-line bounce between cores costs time. State is the enemy of parallelism: a stage that carries filter memory or a running phase accumulator cannot simply be sharded across blocks, so the natural parallel boundaries fall between independent channels or at stateless stages. A block scheduler decides which stage runs on which thread when, balancing load and keeping the pipeline from stalling.
In practice
The cleanest parallelism in an SDR receiver is usually per channel: once a wideband capture is split into several narrowband channels, each channel’s decode chain is independent and maps to its own thread with no shared state — a natural fit for a scanner watching many channels. Within a single channel, pipeline parallelism across the stages is the common structure, with data-parallel workers reserved for genuinely stateless heavy lifting (large FFTs, batched correlation). Multithreading is orthogonal to SIMD vectorization: threads scale across cores while SIMD scales within a core’s inner loop, and a well-tuned DSP program uses both. It is also a source of subtle bugs — races, deadlocks, and priority inversion — which is why disciplined concurrency primitives matter as much as raw core count.
Relevance to SDR
Every serious SDR framework is multithreaded: GNU Radio gives each block its own thread and a scheduler moves data between them; channelized scanners run one decoder thread per channel. GopherTrunk is written in Go, whose goroutines and channels make this style natural — lightweight concurrent workers connected by typed queues rather than manual OS threads and locks. GopherTrunk uses that model to run its capture, down-conversion, and per-channel decode concurrently, so the control-channel decoder and voice decoders progress in parallel and the runtime spreads them across available cores. This is one of the places the language genuinely helps an SDR application: the concurrency model that Go was built around maps directly onto the pipeline-and-per-channel parallelism that real-time software radio needs. The honest caveat is that concurrency is not free performance — it only helps when the work is genuinely parallel and the synchronization overhead stays below the savings.
Sources
-
Data parallelism — Wikipedia, on splitting identical work across processing units, the basis of data-parallel DSP. ↩