The Base.Random implementation of per-thread global RNGs seems to be a pattern for thread-safe mutations in Julia. I’m trying to implement something similar for a problem that strongly benefits from caching intermediate results for future calls to use.
However, I don’t understand the reasoning behind the one line in the __init__()
function. Here’s the Base.Random.RNGs source from master,
const THREAD_RNGs = MersenneTwister[]
@inline default_rng() = default_rng(Threads.threadid())
@noinline function default_rng(tid::Int)
0 < tid <= length(THREAD_RNGs) || _rng_length_assert()
if @inbounds isassigned(THREAD_RNGs, tid)
@inbounds MT = THREAD_RNGs[tid]
else
MT = MersenneTwister()
@inbounds THREAD_RNGs[tid] = MT
end
return MT
end
@noinline _rng_length_assert() = @assert false "0 < tid <= length(THREAD_RNGs)"
function __init__()
resize!(empty!(THREAD_RNGs), Threads.nthreads()) # ensures that we didn't save a bad object
end
My naive approach would have been to write an __init__()
which creates nthreads()
copies of MersenneTwister()
. Why is it preferable to fill an array with undef?