Code readingExplainer

Parallel barcode decoding with multiple threads: safe and worth it

When parallel barcode decoding on multiple threads is safe, how to size worker count from decode time and frame rate, and when one loop still wins.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Is it safe to call barcode decoding from several threads at once?
  2. 02How many worker threads should a four-core machine get?
  3. 03When does a single decode loop still beat multiple threads?
  4. 04How much speedup do extra cores actually deliver?
  5. 05What goes wrong first when parallel decoding fails?
  6. 06Do fixed code readers and smart cameras already do this for you?
  7. 07How do you measure decode time before you size the pool?
  8. 08Which controller has the cores to run parallel decoding?
  9. 09Questions

A four-core vision controller that decodes one image at a time leaves three quarters of its compute idle. The temptation is to throw four threads at the decode loop. Two questions decide it: are concurrent decode calls safe, and does the image rate actually need them? You get the thread-safety test, a load formula that turns decode time and frame rate into a worker count, a worked four-core example with the arithmetic shown, and the failure modes to design out. The method applies to PC-side decode software — a vision controller or industrial PC pulling frames from GigE or USB3 cameras — not to self-contained code readers, which decode on their own silicon.

Is it safe to call barcode decoding from several threads at once?

Yes, when the decode call keeps all of its working state on the stack and spawns no threads of its own, and you give each concurrent call its own input buffer and its own result object; then no locks are needed around the decode itself.

That property has a name: thread safety — code that can run concurrently without race conditions or shared-state surprises. The decode engines behind most PC-based code reading have it because they were built as pure functions. One call takes an image and returns a result set, everything the call touches lives on the stack or in the objects you pass it, and nothing survives between calls. Two calls running at once therefore have nothing to fight over.

Two designs break the pattern, and both are easy to check before you build on them. A decoder that returns a reference into an internal, reused result buffer hands you a race on a plate, because the next call overwrites what an earlier thread is still reading — copy the result out before the next decode. And a library that builds tables or models lazily on first call needs a one-time guard at startup: one barrier before the workers start, not locks around every image.

How many worker threads should a four-core machine get?

Size workers from load, not from core count alone. Multiply decode time per image by images per second to get the core load, then run one worker per core of load with 25 percent headroom, capped at one less than the physical core count.

load = t_decode × image_rate [cores: s × images/s = CPU-seconds per second] workers = ceil(1.25 × load), capped at physical cores − 1

Work the worst case first. The station runs 12 MP frames, the densest real image takes 150 ms to decode on one core, and the line demands 20 images/s.

  • Load: 0.150 s × 20 images/s = 3.0 cores
  • Workers: ceil(1.25 × 3.0) = ceil(3.75) = 4, capped at 4 − 1 = 3
  • Capacity: 3 workers ÷ 0.150 s per image = 20 images/s

Three workers deliver exactly the demand: zero margin, with one core left for the operating system, acquisition and the PLC link. That is the signature of an undersized machine. On a six-core CPU the same duty runs four workers — capacity 4 ÷ 0.150 s = 26.7 images/s, a 33 percent margin, with two cores still free. Where the cap does not bite, the formula leaves real headroom: a load of 2.0 cores needs ceil(2.5) = 3 workers, which run at 67 percent utilization.

When does a single decode loop still beat multiple threads?

Stay single-threaded while the load sits at or under about 0.7 cores. One decode loop inside the acquisition loop is easier to debug, keeps results in order automatically, and cannot drop or reorder a frame.

The 0.7 figure is a utilization rule: a loop running at 100 percent has nothing left for spikes — a dense code, a burst of parts, a logging write — and its queue grows within seconds. The common case lands well under it. A 1.3 MP frame decodes in 45 ms and the line runs 15 images/s: load = 0.045 s × 15 images/s = 0.675 cores. One core covers it with a third of a core in hand, and the loop's ceiling is 1 ÷ 0.045 s = 22 images/s against 15 demanded. Four workers would buy nothing and would cost the automatic ordering and simple debugging that make a single loop easy to trust. With decode that far from saturation, any frame-rate problem lives upstream, in the gap between acquisition and processing speed.

Threads also do not make one image decode faster. A 150 ms decode takes 150 ms whether four workers idle beside it or none; parallel decoding buys images per second, not milliseconds per image. If the complaint is per-image latency, the fix is a smaller ROI, a lower resolution or a faster clock — not a thread pool.

How much speedup do extra cores actually deliver?

Expect near-linear scaling from a stateless decode loop: four workers on four free cores deliver roughly 3.4 to 3.8 times one core's throughput, with the shortfall set by whatever small serial piece remains around the decode.

speedup = 1 / (s + (1 − s) / N)

Here s is the serial fraction of the cycle — queue hand-off, result routing, anything that must happen one at a time — and N is the worker count. Decode work keeps s small. With s = 5 percent and N = 4, speedup = 1 ÷ (0.05 + 0.95 ÷ 4) = 1 ÷ 0.2875 = 3.48. Let the serial fraction double to 10 percent and the same four workers return 1 ÷ 0.325 = 3.08. At eight workers and 5 percent the ceiling is 1 ÷ 0.16875 = 5.9, which is why worker counts beyond six to eight stop paying for this kind of job.

The serial fraction is where the design lives. Keep capture off the decode path — one camera handle owned by the capture thread, frames handed out through a queue — and measured scaling stays near the 3.5 mark on four cores.

What goes wrong first when parallel decoding fails?

The first failures are shared buffers and shared results. Two workers decoding the same bytes, or one overwriting a frame while another reads it, produce intermittent empty or mixed decodes that never reproduce when you step through in a debugger.

Four failures cover nearly every report:

  1. One shared capture buffer. The acquisition thread writes the next frame into the buffer a worker is still decoding. Hand each worker its own copy, or double-buffer — the buffer-preservation habit for reliable reads is exactly this discipline.
  2. Shared results and loggers. Two threads appending to one result object or one file handle interleave silently. Give each worker a private result and serialize only the final write.
  3. Out-of-order results. Finish order follows decode time, not part order. Match every result to its part by timestamp or frame ID, as in timestamp-based result matching, and judge each decode on confidence before acting on it — see how to judge whether a decode is trustworthy.
  4. Two processes, one camera. A pool that re-opens the camera per thread breaks the one-camera-per-process rule; keep capture in one place and hand out frames, per one camera per process.

Do fixed code readers and smart cameras already do this for you?

A fixed code reader or smart camera decodes inside its own processor on its own schedule, so thread pools do not apply to it. Parallel decoding is a technique for PC-side software that pulls frames from network cameras into a vision controller.

The parallelism question only exists where you own the decode software. On a self-contained reader the firmware pipelines capture, preprocessing and decode across its internal cores, and the rating on the page already nets it out: the LS-CRD-0028 is a 5 MP reader rated at 30 fps and 60 codes/s, a decode capacity above its own frame rate. That headroom is the same load test, done for you inside the box.

A smart camera is the same story: inspection runs on the device, no pool of yours needed. Thread pools enter when the design is PC-side — network cameras feeding decode software on a vision controller, the path described in camera barcode decoding from image to validated data. And when one reader's rating cannot cover the duty, faster-rated units sit in the fixed code reader category.

Code readers & vision sensorsFixed code readers

1D/2D codes on the line

658 models · from $399.00

How do you measure decode time before you size the pool?

Measure decode time on the densest real image, at the exact resolution, ROI and code content the station will run. Quote the 95th percentile, not the mean — the mean hides the occasional dense or blurry frame that decides whether the pool saturates.

Log decode duration per image across a few hundred production frames and take the 95th percentile as t_decode. Time the whole path from raw frame to decoded string: preprocessing steps such as rotation, blur and glare correction cost milliseconds like any other stage and belong inside t_decode. Keep CPU time apart from wall-clock time when you instrument the loop — the distinction in CPU time versus wall-clock benchmarking is what stops a parallel run from being measured against the wrong baseline.

Which controller has the cores to run parallel decoding?

Pick the controller by the load number. An entry N-series CPU carries one or two camera streams, a Core i3 with four physical cores carries loads to about 2.5 cores, and loads of three cores or more need a six-core-or-better Core i5 or i7.

At the low end, the MV-VC2140-128G40-1T runs an Intel N97 with 8 GB of memory, a 128 GB SSD and a 1 TB hard drive for image logs. An N97 carries four efficiency cores — enough for a single decode loop and its housekeeping, not for a worker pool.

The MV-VC3301H-128G66 runs an Intel Core i3-8100 with 8 GB of memory and a 128 GB SSD. That CPU has four physical cores without hyperthreading, and it is the right tier while load stays under about 2.5 cores: a 150 ms decode at 12 images/s gives 0.150 s × 12 images/s = 1.8 cores, so workers = ceil(1.25 × 1.8) = 3, capacity 3 ÷ 0.150 s = 20 images/s against 12 demanded, with one core free.

For loads of that size, this is the machine to start from.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

For duties beyond that, the MV-VC5525-256G70-NN steps up to a 12th-generation Core i5 with 32 GB of memory, a 256 GB SSD, PoE camera ports and eight built-in light channels. A desktop Core i5 of that generation has six or more physical cores, which runs four decode workers and still leaves two cores for acquisition, logging and communication. The three tiers sit side by side below.

Specification MV-VC2140-128G40-1T MV-VC3301H-128G66 MV-VC5525-256G70-NN
CPU Intel® Processor N97 Intel® Core i3-8100 Intel® Core i5-12400
Memory 8 GB 8 GB 32 GB
Storage 128 GB SSD + 1 TB HDD 128G SSD 256 GB SSD
GPU Intel® UHD Graphics Intel® HD Graphics 630 Intel® UHD Graphics 730
Operating system Unactivated 64-bit Windows 10 LTSC 21H2 Windows 10 64-bit, Not activated Unactivated Windows 10
Encryption method Not support VM6200Pro license key –
Video output HDMI interface × 1, VGA interface × 1; Supports independent display outputs at the same time; Max. resolution 1920 × 1080 @ 60 Hz HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz
Digital I/O Opto-isolated input × 8; Opto-isolated output × 8 8 digital isolated inputs supported; 16 opto-isolated outputs supported (NPN/PNP switchable) Opto-isolated input × 8; Opto-isolated output × 12 ( supports NPN/PNP switching), digital-isolated low-side driver output × 8; Non-isolated bi-directional differential I/O × 4
Light interface Light interface with 24 VDC constant voltage × 4; (Brightness is adjustable. Max. current 3 A for single light interface, and total max. power consumption 80 W. Supports light mode switching between strobe and solid via external control) 4-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. power (4 channels): 150 W 24 VDC constant voltage × 8; (Max. power consumption of single channel is 72 W, total max. power consumption 190 W)
Network interface GigE interface × 4 6× Gigabit Ethernet ports GigE interface × 7, including interface for PoE × 4
USB Interface USB 2.0 × 2; USB 3.0 × 2 USB 2.0 × 4; USB 3.0 × 4; Built-in USB 2.0 × 1 USB 2.0 × 4, USB 3.0 × 4, built-in USB 2.0 × 1
Serial Port RS-232 × 1, supports switching to RS-485 or RS-422 2× full-function RS-232, RS-485/RS-422 switchable RS-232 × 2 by default, supports switching to RS-485 and RS-422
Price $1,679.00 $2,419.00 $4,479.00 · quoted

All 3 share: Certification CE, KC. Values as printed in each product's data sheet; open a part for its drawing and full specifications.

Five numbers settle the design before you order anything:

  • t_decode at the 95th percentile, on the densest real image, in milliseconds
  • Load in cores: t_decode × image rate
  • Worker count: ceil(1.25 × load), capped at physical cores − 1
  • Margin: worker capacity (workers ÷ t_decode) against demand, target 25 percent or more
  • Overflow behavior: fail-safe wired to the reject output or a line stop

If you send frame rate, image size and code layout to the team through the contact page, you get a load number and a controller short-list back. More decode-side material sits under the code-reading guides, and barcode reader verification for label inspection covers what to demand of a decode result before you trust a pooled reader on the line.

Questions

How do I prove a decode pool is free of races before it goes on the line?

Run the same set of a few hundred production images through the pool twice and compare the results byte for byte. A race shows up as an image that decodes on one run and fails on the next, or returns a different string; a deterministic pool returns identical output every time. Repeat the test after any change to buffer handling, not only once at commissioning.

Should I use separate processes instead of threads for decoding?

Processes earn their keep in runtimes that serialize threaded code behind a global interpreter lock, and where one crashed worker must not take the whole station down. The cost is copying or sharing images and results across a process boundary, which adds latency and serialization code. For a compiled decode path, threads are lighter and normally enough; in locked interpreters, one process per camera is the pattern that scales.

How much does the number of codes in an image change decode time?

Decode time follows the number of candidate regions, not just pixel count: an image with one clean code may take 40 ms, while six codes in the same frame can take three to four times longer because the decoder works each region separately. Budget per expected code count and measure with the densest real image before sizing the worker pool.

How deep should the queue between capture and the workers be?

Keep it near twice the worker count. A deep queue hides a saturated pool, adds latency, and fills memory with frames that are already stale; when it fills, drop the oldest frame and count the drop, because a decode result for a part that has left the station is worth nothing.

Do I need to pin worker threads to specific cores?

Normally no: the scheduler balances independent per-image work well, and pinning buys little for decode loops. Pin only when a jitter-sensitive task shares the machine, and then reserve one core for the operating system and communication stacks so the decode workers never compete with the link to the PLC.