Reagents.jl is still work-in-progress.
Example: Treiber stack
using ReagentsTreiber stack is a simple concurrent data structure where an immutable list holds the data items
struct TSNode{T}
head::T
tail::Union{TSNode{T},Nothing}
end
const TSList{T} = Union{TSNode{T},Nothing}... which is referenced through an atomically updatable memory location (here, a Reagents.Ref):
struct TreiberStack{T,Ref<:Reagents.Ref{TSList{T}}}
head::Ref
end
Base.eltype(::Type{<:TreiberStack{T}}) where {T} = TAn empty stack can be constructed as a reference to the empty list; i.e., nothing:
TreiberStack{T}() where {T} = TreiberStack(Reagents.Ref{TSList{T}}(nothing))To push an element to the stack, we can use Reagents.Update
pushing(stack::TreiberStack) =
Reagents.Update((xs, x) -> (TSNode(x, xs), nothing), stack.head)Let's see how it works.
using Test
function test_pushing()When a stack is created, its head points to nothing:
stack = TreiberStack{Int}()
@test stack.head[] === nothing # emptyWe can create a reagent for pushing a value to the stack:
reagent = pushing(stack)However, note that reagent does nothing when it's created.
@test stack.head[] === nothing # emptyThe reagent must be executed ("react") to invoke its side-effect:
reagent(111)
@test stack.head[] === TSNode(111, nothing)
endSimilarly, we can pop off an element from the stack, again by Reagents.Update. To support empty stack, we return nothing when it's empty and return Some(value) when we find a value:
trypopping(stack::TreiberStack) =
Reagents.Update(stack.head) do xs, _ignored
if xs === nothing
return (nothing, nothing)
else
return (xs.tail, Some(xs.head))
end
endHere's how it works.
function test_trypopping()Let's push 111 and then 222 using the pushing reagent:
stack = TreiberStack{Int}()
pushing(stack)(111)
pushing(stack)(222)We can invoke the reagent trypopping(stack) by calling it. Since trypopping ignores the input (see the argument _ignored in trypopping definition above), we can pass an arbitrary value to the reagent, e.g., nothing.
@test trypopping(stack)(nothing) === Some(222)For convinience, nothing is the default argument when the reagent is called without an argument:
@test trypopping(stack)() === Some(111)Now that all values are popped, invoking trypopping returns nothing:
@test trypopping(stack)() === nothing
endIt is simple to wrap these reagents into the Base API:
Base.push!(stack::TreiberStack, value) = pushing(stack)(convert(eltype(stack), value))
Base.pop!(stack::TreiberStack) = something(trypopping(stack)())Note that this version of pop!(stack::TreiberStack) throws when the stack is empty. See Blocking containers for a generic derivation of a blocking version of pop! that waits for the value to be push!ed.
For more usage examples, see /test/ReagentsTests/src/test_treiberstack.jl.
This page was generated using Literate.jl.