Concurrent work-stealing deques.
These data structures are most commonly used in work-stealing schedulers. The typical setup involves a number of threads, each having its own FIFO or LIFO queue (worker). There is also one global FIFO queue (injector) and a list of references to worker queues that are able to steal tasks (stealers).
We spawn a new task onto the scheduler by pushing it into the injector queue. Each worker thread waits in a loop until it finds the next task to run and then runs it. To find a task, it first looks into its local worker queue, and then into the injector and stealers.
Queues
[Injector] is a FIFO queue, where tasks are pushed and stolen from opposite ends. It is
shared among threads and is usually the entry point for new tasks.
[Worker] has two constructors:
new_fifo()- Creates a FIFO queue, in which tasks are pushed and popped from opposite ends.new_lifo()- Creates a LIFO queue, in which tasks are pushed and popped from the same end.
Each [Worker] is owned by a single thread and supports only push and pop operations.
Method stealer() creates a [Stealer] that may be shared among threads and can only steal
tasks from its [Worker]. Tasks are stolen from the end opposite to where they get pushed.
Stealing
Steal operations come in three flavors:
steal()- Steals one task.steal_batch()- Steals a batch of tasks and moves them into another worker.steal_batch_and_pop()- Steals a batch of tasks, moves them into another queue, and pops one task from that worker.
In contrast to push and pop operations, stealing can spuriously fail with [Steal::Retry], in
which case the steal operation needs to be retried.
Examples
Suppose a thread in a work-stealing scheduler is idle and looking for the next task to run. To find an available task, it might do the following:
- Try popping one task from the local worker queue.
- Try stealing a batch of tasks from the global injector queue.
- Try stealing one task from another thread using the stealer list.
An implementation of this work-stealing strategy:
Examples
use WorkStealQueue;
let queue = new;
queue.push;
queue.push;
let local0 = queue.local_queue;
local0.push_back;
local0.push_back;
local0.push_back;
local0.push_back;
let local1 = queue.local_queue;
local1.push_back;
local1.push_back;
for i in 0..8