Field Guide · concept

Also known as: scheduler, flowgraph runtime, block scheduler

A block scheduler is the runtime that drives a flowgraph, deciding when to invoke each block’s work function and managing the sample buffers that sit between blocks.1 The blocks describe what the DSP does; the scheduler decides when each block runs, how much data it gets, and where its output goes. It is the engine that turns a static graph of signal-processing blocks into a running radio, and its design largely determines the throughput and latency of the whole system.

block A block B block C buffer buffer scheduler
The scheduler watches each inter-block buffer and calls a block's work function only when it has enough input and room downstream to produce output.

How it works

The scheduler’s core loop asks, for each block: does it have enough input available, and is there enough free space in its output buffer to write results? If both are true, it calls the block’s work function with pointers to the available input and output, then advances the buffer read and write pointers by however much the block reported consuming and producing.

Two coupled resources decide when a block may run:

  • Input availability — a block cannot run until upstream has produced enough samples (a decimator that needs 8 inputs per output waits for at least 8).
  • Output space — a block cannot run if the downstream buffer is full. This is back-pressure: a slow consumer stalls its producer, which naturally throttles the whole upstream chain so no buffer overflows.

Because each edge has a finite buffer, the scheduler never lets a fast block run away and exhaust memory — the graph self-regulates to the speed of its slowest stage.

In practice

GNU Radio’s classic scheduler is thread-per-block (TPB): every block gets its own OS thread, and threads block and wake on buffer condition variables. This maps cleanly onto multi-core CPUs — independent branches of the graph run in parallel — but incurs a context switch and synchronization cost per scheduling decision, so very fine-grained blocks can spend more time coordinating than computing. Alternative schedulers (GNU Radio’s newer runtime work, and other frameworks) explore single-threaded execution, thread pools, or fused blocks to reduce that overhead.

Buffer sizing is the scheduler’s main latency knob. Large buffers amortize per-call overhead and raise throughput but add latency, because a sample sits in the pipeline longer before it is processed; small buffers cut latency but schedule more often. For real-time receive from live hardware, the scheduler must also keep up on average or the source’s own buffer overruns and samples are lost.

Relevance to SDR

Whether or not you use GNU Radio, some component has to answer “which stage runs next, with how much data” — and that is the scheduler’s question. Understanding it explains the two effects every SDR developer eventually meets: back-pressure (why a slow decoder can stall the whole chain, or conversely why a stalled sink causes upstream overruns) and the throughput/latency trade-off buried in buffer sizes.

GopherTrunk does not embed a GNU Radio scheduler; being pure Go, it lets the Go runtime schedule goroutines and uses buffered channels as the inter-stage buffers, so back-pressure falls out of channel capacity rather than an explicit DSP scheduler. The mechanism differs but the invariants are the same: a bounded buffer between each stage, a consumer that can stall its producer, and buffer size as the throughput-versus- latency dial. Reading a GopherTrunk decode pipeline with the block-scheduler model in mind makes it clear why a wedged downstream stage shows up as dropped samples at the radio source.

Sources

  1. Blocks Coding Guide — GNU Radio Wiki, on how the runtime calls work()/general_work(), consumes and produces items, and manages inter-block buffers. 

See also