Reagents.jl is still work-in-progress.
Example: Treiber stack
using Reagents
Treiber 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} = T
An 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 # empty
We 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 # empty
The reagent must be executed ("react") to invoke its side-effect:
reagent(111)
@test stack.head[] === TSNode(111, nothing)
end
Similarly, 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
end
Here'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
end
It 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.