Concurrent::Queue
A lock-free queue data structure, safe for concurrent use.
Synopsis
use Concurrent::Queue;
my $queue = Concurrent::Queue.new;
$queue.enqueue('who');
$queue.enqueue('what');
$queue.enqueue('why');
say $queue.dequeue; # who
say ?$queue; # True
say $queue.elems; # 2
say $queue.Seq; # (what why)
say $queue.list; # (what why)
say $queue.dequeue; # what
$queue.enqueue('when');
say $queue.dequeue; # why
say $queue.dequeue; # when
say ?$queue; # False
say $queue.elems; # 0
my $val = $queue.dequeue;
say $val.WHO; # Failure
say $val.exception.WHO; # X::Concurrent::Queue::Empty
Overview
Lock-free data structures may be safely used from multiple threads, yet do not
use locks in their implementation. They achieve this through the use of atomic
operations provided by the hardware. Nothing can make contention between
threads cheap - synchronization at the CPU level is still synchronization -
but lock-free data structures tend to scale better.
This lock-free queue data structure implements an algorithm described by
Maged M. Michael and Michael L. Scott.
The only (intended) differences are:
- A
Failure
is returned to indicate emptiness, rather than a combination of
boolean return value and out parameter, in order that this type feels more
natural to Perl 6 language users - There is an out-of-band element count (which doesn't change the algorithm at
all, just increments and decrements the count after an enqueue/dequeue)
- Perl 6 doesn't need ABA-problem mitigation thanks to having GC
The elems
and Bool
method should not be used to decide whether to dequeue,
unless it is known that no other thread could be performing an enqueue or
dequeue. Their only use in the presence of concurrent use of the queue is for
getting an approximate idea of queue size. In the presence of a single thread,
the element count will be accurate (so if many workers were to enqueue data,
and are known to have completed, then at that point the elems
will be an
accurate reflection of how many values were placed in the queue).
Note that there is no blocking dequeue operation. If looking for a blocking
queue, consider using the Perl 6 built-in Channel
class. (If tempted to
write code that sits in a loop testing if dequeue
gives back a Failure
-
don't. Use Channel
instead.)
Methods
enqueue(Any $value)
Puts the value into the queue at its tail. Returns Nil
.
dequeue()
If the queue is not empty, removes the head value and returns it. Otherwise,
returns a Failure
containing an exception of type X::Concurrent::Queue::Empty
.
elems()
Returns the number of elements in the queue. This value can only be relied upon
when it is known that no threads are interacting with the queue at the point this
method is called. Never use the result of elems
to decide whether to dequeue
,
since another thread may enqueue
or dequeue
in the meantime. Instead, check
if dequeue
returns a Failure
.
Bool()
Returns False
if the queue is empty and True
if the queue is non-empty.
The result can only be relied upon when it is known that no threads are
interacting with the queue at the point this method is called. Never use
the result of Bool
to decide whether to dequeue
, since another thread
may enqueue
or dequeue
in the meantime. Instead, check if dequeue
returns a Failure
.
Seq
Returns a Seq
that will iterate the queue contents. The iteration will
include all values that had not been dequeued at the point the Seq
method was called. Additionally, it will incorporate any values that
are enqueued during the iteration, meaning that if values are being
enqueued at a rate at least as fast as the iteration is visiting them
then the iteration may not terminate.
If wanting to prevent this, consider limiting the result length to
that of the result of elems
(e.g. $cq.head($cq.elems)
). If
using the queue to collect values in many threads and then iterate
them in one thread afterwards, this is not a concern, since nothing
will be enqueueing further values at that point. However, consider
using Concurrent::Stack
instead, since a concurrent stack's
operations are cheaper than those of a concurrent queue (the same
algorithmic order, but a lower constant factor).
list
Equivalent to .Seq.list
; see the description of Seq
for caveats,
and remember that a List
preserves its elements, so the potentially
endless iteration could also eat endless memory.