Thread safe buffer correctness

For Channel-based producer-consumer approach with consumer-local buffer reuse, see the first example in Pattern for managing thread local storage? - #2 by tkf

This pattern is very handy and performs reasonably well in many situations. But, since the whole purpose of Channel is to serialize the access, it’s not really the best approach. If your input collection is supported by SplittablesBase.jl, FLoops.jl’s @init can be useful.

But, if you need a buffer pool that works outside producer-consumer pattern or parallel loop, I see that something like SafeBuffer in the OP could be handy. One way to simplify the synchronization is:

struct BufferPool{T}
    buffers::Vector{T}
    condition::Threads.Condition
end

function bufferpool(factory, n::Integer)
    n > 0 || error("need at least one buffer; got n = $n")
    return BufferPool([factory() for _ in 1:n], Threads.Condition())
end

function withbuffer(f, pool::BufferPool)
    b = lock(pool.condition) do
        while isempty(pool.buffers)
            wait(pool.condition)
        end
        pop!(pool.buffers)
    end
    try
        f(b)
    finally
        lock(pool.condition) do
            push!(pool.buffers, b)
            notify(pool.condition)
        end
    end
end
5 Likes