ConcurrentCollections.jl
ConcurrentCollections.ConcurrentDict
ConcurrentCollections.Delete
ConcurrentCollections.ConcurrentQueue
ConcurrentCollections.ConcurrentStack
ConcurrentCollections.DualLinkedConcurrentRingQueue
ConcurrentCollections.DualLinkedQueue
ConcurrentCollections.LinkedConcurrentRingQueue
ConcurrentCollections.WorkStealingDeque
ConcurrentCollections.Keep
ConcurrentCollections.maybeget
ConcurrentCollections.maybepop!
ConcurrentCollections.maybepopfirst!
ConcurrentCollections.modify!
Queue/stack
ConcurrentCollections.DualLinkedConcurrentRingQueue
— TypeDualLinkedConcurrentRingQueue{T}()
A concurrent queue with "almost" nonblocking push!
and popfirst!
. Calling popfirst!
on an empty queue waits for a push!
in another task.
See also: LinkedConcurrentRingQueue
, DualLinkedQueue
Examples
julia> using ConcurrentCollections
julia> q = DualLinkedConcurrentRingQueue{Int}();
julia> push!(q, 111);
julia> push!(q, 222);
julia> popfirst!(q) # first-in first-out
111
julia> popfirst!(q)
222
Extended help
Since popfirst!
blocks when called on an empty queue, a DualLinkedConcurrentRingQueue
acts almost like an unbounded Base.Channel
. However, DualLinkedConcurrentRingQueue
does not support close
or blocking on push!
when exceeding a bound.
DualLinkedConcurrentRingQueue
performs very well compared to other concurrent queue implementations. However, since it is based on linked fixed-size buffers, it has relatively large memory overhead.
DualLinkedConcurrentRingQueue
is based on the linked multi-polarity dual ring queue by Izraelevitz and Scott (2017):
Izraelevitz, Joseph, and Michael L. Scott. “Generality and Speed in Nonblocking Dual Containers.” ACM Transactions on Parallel Computing 3, no. 4 (March 23, 2017): 22:1–22:37. https://doi.org/10.1145/3040220.
ConcurrentCollections.DualLinkedQueue
— TypeDualLinkedQueue{T}()
A concurrent queue with nonblocking push!
and popfirst!
. Calling popfirst!
on an empty queue waits for a push!
in another task.
DualLinkedConcurrentRingQueue
provides a faster dual queue with a larger memory footprint.
Examples
julia> using ConcurrentCollections
julia> q = DualLinkedQueue{Int}();
julia> push!(q, 111);
julia> push!(q, 222);
julia> popfirst!(q) # first-in first-out
111
julia> popfirst!(q)
222
Extended help
Since popfirst!
blocks when called on an empty queue, a DualLinkedQueue
acts almost like an unbounded Base.Channel
. However, DualLinkedQueue
does not support close
or blocking on push!
when exceeding a bound.
DualLinkedQueue
implements the dual queue by Scherer and Scott (2004):
Scherer, William N., and Michael L. Scott. “Nonblocking Concurrent Data Structures with Condition Synchronization.” In Distributed Computing, edited by Rachid Guerraoui, 174–87. Lecture Notes in Computer Science. Berlin, Heidelberg: Springer, 2004. https://doi.org/10.1007/978-3-540-30186-8_13.
ConcurrentCollections.LinkedConcurrentRingQueue
— TypeLinkedConcurrentRingQueue{T}()
A concurrent queue with nonblocking push!
and maybepopfirst!
.
See also: DualLinkedConcurrentRingQueue
Examples
julia> using ConcurrentCollections
julia> q = LinkedConcurrentRingQueue{Int}();
julia> push!(q, 111);
julia> push!(q, 222);
julia> maybepopfirst!(q) # first-in first-out
Some(111)
julia> maybepopfirst!(q)
Some(222)
julia> maybepopfirst!(q) === nothing # queue is empty
true
Extended help
LinkedConcurrentRingQueue
is based on Linked Concurrent Ring Queue (or List of Concurrent Ring Queues; LCRQ) by Morrison and Afek (2013):
Morrison, Adam, and Yehuda Afek. “Fast Concurrent Queues for X86 Processors.” In Proceedings of the 18th ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, 103–112. PPoPP ’13. New York, NY, USA: Association for Computing Machinery, 2013. https://doi.org/10.1145/2442516.2442527. (Revised version: https://www.cs.tau.ac.il/~mad/publications/ppopp2013-x86queues.pdf)
ConcurrentCollections.ConcurrentQueue
— TypeConcurrentQueue{T}()
Concurrent queue of objects of type T
.
Use push!
to insert an element at the tail and maybepopfirst!
to retrieve and remove an element at the head.
Implementation detail: It implements the Michael and Scott queue.
Examples
julia> using ConcurrentCollections
julia> queue = ConcurrentQueue{Int}();
julia> push!(queue, 1);
julia> push!(queue, 2);
julia> popfirst!(queue)
1
julia> maybepopfirst!(queue)
Some(2)
julia> maybepopfirst!(queue) # returns nothing
ConcurrentCollections.ConcurrentStack
— TypeConcurrentStack{T}()
Concurrent stack of objects of type T
.
Use push!
to insert an element and maybepop!
to retrieve and remove an element.
It implements the Treiber stack.
Examples
julia> using ConcurrentCollections
julia> stack = ConcurrentStack{Int}();
julia> push!(stack, 1);
julia> push!(stack, 2);
julia> pop!(stack)
2
julia> maybepop!(stack)
Some(1)
julia> maybepop!(stack) # returns nothing
ConcurrentCollections.WorkStealingDeque
— TypeWorkStealingDeque{T}()
Concurrent work-stealing "deque" of objects of type T
.
This is not a full deque in the sense that:
push!
andmaybepop!
operating at the tail of the collection can only be executed by a single task.maybepopfirst!
(aka steal) for retrieving and removing an element at the head can be invoked from any tasks. However, there is nopushfirst!
.
Examples
julia> using ConcurrentCollections
julia> deque = WorkStealingDeque{Int}();
julia> push!(deque, 1);
julia> push!(deque, 2);
julia> push!(deque, 3);
julia> maybepop!(deque)
Some(3)
julia> fetch(Threads.@spawn maybepopfirst!(deque))
Some(1)
julia> fetch(Threads.@spawn popfirst!(deque))
2
julia> maybepopfirst!(deque) # returns nothing
Extended help
The current implementation is known to be not fully compliant with the C/C++ memory model (on which Julia's memory model is designed).
It implements the dynamic circular work-stealing deque by Chase and Lev (2005):
Chase, David, and Yossi Lev. “Dynamic Circular Work-Stealing Deque.” In Proceedings of the Seventeenth Annual ACM Symposium on Parallelism in Algorithms and Architectures, 21–28. SPAA ’05. New York, NY, USA: Association for Computing Machinery, 2005. https://doi.org/10.1145/1073970.1073974.
ConcurrentCollections.maybepop!
— Functionmaybepop!(collection) -> Some(value::T) or nothing
Try to pop a value
from the tail of collection
. Return Some(value)
if it is non-empty. Return nothing
if empty.
Examples
julia> using ConcurrentCollections
julia> stack = ConcurrentStack{Int}();
julia> push!(stack, 1);
julia> maybepop!(stack)
Some(1)
julia> maybepop!(stack) # returns nothing
ConcurrentCollections.maybepopfirst!
— Functionmaybepopfirst!(collection) -> Some(value::T) or nothing
Try to pop a value
from the head of collection
. Return Some(value)
if it is non-empty. Return nothing
if empty.
Examples
julia> using ConcurrentCollections
julia> queue = ConcurrentQueue{Int}();
julia> push!(queue, 1);
julia> maybepopfirst!(queue)
Some(1)
julia> maybepopfirst!(queue) # returns nothing
Hash table
ConcurrentCollections.ConcurrentDict
— TypeConcurrentDict{K,V}()
Concurrent dictionary. All operations are lock-free except when the dictionary is resized.
Although tasks wait
on concurrent modifications (e.g., setindex!
) during resize, the worker threads participate in the resize to avoid wasting CPU resources.
Examples
julia> using ConcurrentCollections
julia> dict = ConcurrentDict{String,Int}();
julia> dict["hello"] = 1;
julia> dict["hello"]
1
ConcurrentCollections.modify!
— Functionmodify!(f, dict::ConcurrentDict{K,V}, key::K) -> y
Atomically update key
slot of dict
using a function f
.
If key
does not exist, f
is called with nothing
. The call f(nothing)
must return either (1) nothing
to keep the slot unoccupied or (2) Some(value::V)
to insert value
.
If key
exist, f
is called with a ref
such that ref[]
retrieves the value
corresponding to the key
. The call f(ref)
must return either (1) nothing
to delete the slot, (2) Some(value′::V)
to insert value
, (3) Keep(ans)
to return y = Keep(ans)
from modify!
, or (4) Delete(ans)
to delete slot and return a value y = Delete(ans)
from modify!
.
The function f
may be called more than once if multiple tasks try to modify the dictionary.
Examples
julia> using ConcurrentCollections
julia> dict = ConcurrentDict{String,Int}();
julia> modify!(dict, "hello") do _
Some(1)
end
Some(1)
julia> modify!(dict, "hello") do ref
Some(something(ref[]) + 1)
end
Some(2)
ConcurrentCollections.maybeget
— Functionmaybeget(dict::ConcurrentDict{K,V}, key) -> Some(value::T) or nothing
ConcurrentCollections.Keep
— TypeKeep(ans)
A special type used in modify!
to indicate that a slot should be remain unchanged while propagating the result ans
of some computation to the caller.
That is to say,
y = modify!(dict, key) do value
Keep(f(something(value)))
end
y[]
is an optimization of
r = Ref{Any}()
modify!(dict, key) do value
r[] = f(something(value))
Some(value)
end
r[]
ConcurrentCollections.Delete
— TypeDelete(ans)
A special type used in modify!
to indicate that a slot should be removed.
That is to say
y = modify!(dict, key) do value
Delete(f(something(value)))
end
y[]
is an optimization of
r = Ref{Any}()
modify!(dict, key) do value
r[] = f(something(value))
nothing
end
r[]