LabVIEW Producer/Consumer Pattern: Acquire and Log Data Without Losing Samples
Split acquisition and logging into two LabVIEW loops connected by a bounded queue. Covers wiring, clean shutdown with Release Queue, backpressure, and how to verify nothing is dropped.
26 Mar 2026, 11:46 UTC

You have a DAQ loop that must run at a fixed rate, and a logging routine — file writes, analysis, or front-panel updates — that can't keep up with it. Put both in one loop and you either drop samples or stall the UI. The Producer/Consumer pattern solves this by splitting the work into two parallel While Loops connected by a queue: the producer acquires and enqueues data, the consumer dequeues and processes it at its own pace. The queue buffers the mismatch instead of losing it.
What you need before wiring anything
This guide assumes LabVIEW 2018 or later (the queue primitives used here are stable across versions, but confirm against the shipping examples for your installation). You need:
- A working acquisition VI or simulated data source that produces one data element per iteration.
- A defined data type for the queue. Use a cluster containing an enum (message type) and a variant (payload) if you expect to grow this into a message handler; a raw waveform or array works for pure streaming.
- A decision on the maximum queue size. A bounded queue applies backpressure — Enqueue Element blocks when full — which throttles the producer instead of silently dropping data or growing memory without limit.
Building the two loops
Create a new VI with two While Loops side by side. All queue functions live on the block diagram under Functions > Programming > Synchronization > Queue Operations.
1. Create the queue
Drop Obtain Queue above and to the left of both loops. Wire your data type into the element data type input and set max queue size to a deliberate value — for example, 10 seconds worth of samples at your acquisition rate. Leave the name terminal unwired to create an unnamed queue, and pass the reference by wire into both loops. Named queues can collide across VIs in the same application instance, so prefer unnamed references unless you genuinely need cross-VI access.
2. Wire the producer loop
Inside the top loop, place your acquisition code followed by Enqueue Element. Wire the queue reference and the acquired data in, and propagate the error cluster through. The loop's timing comes from your acquisition source (a DAQmx read or a Wait function), not from the queue. If the consumer falls behind and the queue fills, Enqueue Element blocks — that is the intended throttle, not a bug.
3. Wire the consumer loop
Inside the bottom loop, place Dequeue Element, then a Case Structure driven by your enum or a simple processing subVI. Keep front-panel updates here minimal — indicator updates on every iteration can make controls sluggish under heavy data rates. If you need rich UI feedback, decimate the updates (every Nth element) or add a third loop.
Dequeue Element has a timeout in ms input (default -1, wait forever). Set a finite timeout — say 250 ms — if the consumer must also poll other conditions, such as a stop control or a notifier carrying user commands.
4. Shut down cleanly
The standard shutdown sequence matters more than any other part of this pattern:
- Your stop condition (front-panel button, error, or acquisition complete) stops the producer loop first.
- After the producer loop exits, call Release Queue on the queue reference.
- Releasing the queue causes the consumer's pending Dequeue Element to return an error. Wire that error output — through a case selector or directly — to the consumer loop's stop terminal.
- Do not wire the consumer's error cluster to a plain error dialog without filtering, or users will see a spurious shutdown error every time the application closes normally.
With a finite dequeue timeout, the consumer can also drain remaining queued elements before exiting: on timeout, check whether the producer has finished and the queue is empty, then stop.
Checking that it actually works
Structure alone doesn't prove the pattern is doing its job. Verify these four behaviors:
- No backlog at steady state. Drop Get Queue Status in the consumer loop during development and chart the # elements in queue output. Run at your target acquisition rate for several minutes. The count should hover near zero. A steadily climbing count means the consumer is too slow and the queue is only delaying failure.
- Backpressure instead of data loss. Temporarily add a Wait inside the consumer (simulating a slow disk write) with a bounded queue. The producer should slow down — its iteration rate drops — rather than the queue overflowing or samples vanishing.
- Clean stop. Halt mid-acquisition. Both loops should terminate within one iteration of the stop command, and the consumer's error wire should carry only the expected release-queue error, which you handle deliberately.
- Compare against the shipping example. Open the NI Example Finder (Help > Find Examples) and search for the Producer/Consumer Design Pattern (Data) example. Diff your structure against it — especially the Release Queue placement.
Common failure modes
Unbounded queue, slow consumer. If you leave max queue size at -1 (unlimited) and the consumer can't keep up, memory grows until the application fails. Always bound the queue during development and monitor its size.
Confusing queues with notifiers. A notifier keeps only the latest value and drops older ones; a queue buffers everything. For data logging, dropping is usually unacceptable, so use a queue. For a "latest status" display, a notifier is the lighter choice.
UI work in the consumer. Property nodes and heavy graph updates execute on the UI thread and can stall the consumer under load. Offload or decimate them.
Recovery when something goes wrong
If the consumer hangs after shutdown, check that Release Queue actually executes — a broken error wire or an early-aborting producer can skip it. If the producer never throttles, confirm max queue size is wired and not -1. If you see intermittent missed samples at high rates, profile whether acquisition and enqueue share a loop with anything blocking, such as a synchronous file flush; move that work to the consumer where it belongs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.