DynQueue - a parallel work queue that can grow dynamically while it drains
DynQueue is a small Rust library for processing a worklist in parallel where new work can be added while the queue is still being consumed.
Rayon can express dynamically generated work with scoped tasks, but its ordinary parallel iterators are designed around splitting an existing collection. DynQueue provides a different, queue-oriented interface: every item, both initial and newly enqueued, is processed by the same callback. The queue is drained to completion without requiring callers to create a task closure for every item or implement worker lifecycle and termination detection.
Core functionality
- Process a queue in parallel with
for_each_dyn, whose callback can enqueue more work via theDynQueueHandleit receives. - Newly enqueued items are picked up by any idle worker — they are never stuck on the thread that produced them.
- Works over
Vec,VecDequeandcrossbeam_queue::SegQueue(via thecrossbeam-queuefeature). Add your own backing by implementing theQueuetrait.
Use cases
- Tree / graph traversal where new nodes are discovered while visiting.
- Backtracking and search where each step can produce more candidates.
- Any parallel task whose total workload is not known ahead of time.
- Classic worklist algorithms (compiler dataflow analyses, mark-and-sweep, reachability): the canonical shape is exactly one callback over a queue of homogeneous items, where processing an item adds more items — usually serially, and DynQueue is the drop-in way to parallelize it.
Example
use IntoDynQueue as _;
let out = new;
vec!
.into_dyn_queue
.for_each_dyn;
let mut result = out.into_inner.unwrap;
result.sort;
assert_eq!;
Runnable examples live in examples/. dua is a tiny parallel
du: one callback walks the tree, enqueuing the entries of every directory it
reads, and reports filesystem errors as work items instead of panicking.
Compared with a plain Rayon parallel iterator
A dynamic workload processed with a naive par_iter split runs at roughly the
speed of one thread. DynQueue distributes newly generated items across all
workers:
| approach | wall time (4 threads, heavy dynamic workload) |
|---|---|
| strictly sequential | 1.26 s |
| static Rayon split | 1.26 s |
DynQueue for_each_dyn |
~0.32 s (≈ 4×) |
How this relates to Rayon
Rayon's ParallelIterator contract is built around split + fold: work is
partitioned up front and each partition is consumed by one thread. Dynamically
enqueued items cannot be re-partitioned once folding starts, so they stay on the
producing thread. DynQueue instead drains one shared, mutex-guarded worklist from
all workers. This delivers load balancing for dynamically generated items, at the
cost of not composing with map/collect chains.
This does not mean Rayon itself is limited to static work. rayon::scope
lets a task spawn more tasks, and Rayon's work-stealing scheduler distributes them
among workers. Prefer that API when each generated job is naturally a distinct
closure or needs its own captured state. Prefer DynQueue when the workload is a
stream of homogeneous values handled by one callback:
Rayon scope: task -> spawn another closure
DynQueue: item -> enqueue another item -> same callback
Lower-level concurrent and work-stealing queue crates can also implement this
pattern, but require the caller to construct the worker loop and decide when all
recursively generated work is complete. DynQueue packages those pieces into
for_each_dyn while allowing custom storage through the Queue trait.
Constraints
DynQueueHandleis a borrowing handle: it is only valid inside the callback it is given to. You cannot stash it for later — that is a compile error, not a runtime panic.- The callback must be
Send + Sync(shared across worker threads). Use aMutex/channel to write results out of the callback. - Panics inside the callback propagate to the caller.
Features
crossbeam-queue: usecrossbeam::queue::SegQueueas the back-end.
TL;DR
A DynQueue<T> is drained in parallel with for_each_dyn(|handle, item| ...).
Inside the callback, handle.enqueue(new) adds more work that any idle worker
picks up.