Using Threads@threads loop with my own rng

How do I create and pass rng’s to a multi-threaded loop safely? For example, the following code will fail with large K.

function thread_samples(K)
    rng = MersenneTwister(1)
    samples = zeros(Int64, K)
    Threads.@threads for k = 1:K
        samples[k] = rand(rng, 1:10)
    end
end

thread_samples(100000)
ERROR: LoadError: TaskFailedException:
InexactError: check_top_bit(UInt64, -88)
Stacktrace:
 [1] throw_inexacterror(::Symbol, ::Type{UInt64}, ::Int64) at ./boot.jl:558
 [2] check_top_bit at ./boot.jl:572 [inlined]
 [3] toUInt64 at ./boot.jl:683 [inlined]
 [4] UInt64 at ./boot.jl:713 [inlined]
 [5] convert at ./number.jl:7 [inlined]
 [6] cconvert at ./essentials.jl:388 [inlined]
 [7] unsafe_copyto! at ./array.jl:245 [inlined]
 [8] _rand_max383!(::MersenneTwister, ::Random.UnsafeView{Float64}, ::Random.CloseOpen12{Float64}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:452
 [9] rand!(::MersenneTwister, ::Random.UnsafeView{Float64}, ::Random.SamplerTrivial{Random.CloseOpen12{Float64},Float64}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:486
 [10] rand! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/Random.jl:266 [inlined]
 [11] rand!(::MersenneTwister, ::Random.UnsafeView{UInt128}, ::Random.SamplerType{UInt128}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:567
 [12] rand! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:590 [inlined]
 [13] rand! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/Random.jl:267 [inlined] (repeats 2 times)
 [14] mt_setfull! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:215 [inlined]
 [15] reserve1 at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:222 [inlined]
 [16] mt_pop! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:227 [inlined]
 [17] rand at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/RNGs.jl:392 [inlined]
 [18] rand at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/Random.jl:256 [inlined]
 [19] rand(::MersenneTwister, ::Random.SamplerRangeNDL{UInt64,Int64}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/generation.jl:326
 [20] rand at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.5/Random/src/Random.jl:253 [inlined]
 [21] macro expansion at /Users/mfairley/Projects/AdaptiveSurveillance/test/test_threaded_rng.jl:11 [inlined]
 [22] (::var"#2#threadsfor_fun#1"{MersenneTwister,Array{Int64,1},UnitRange{Int64}})(::Bool) at ./threadingconstructs.jl:81
 [23] (::var"#2#threadsfor_fun#1"{MersenneTwister,Array{Int64,1},UnitRange{Int64}})() at ./threadingconstructs.jl:48
Stacktrace:
 [1] wait at ./task.jl:267 [inlined]
 [2] threading_run(::Function) at ./threadingconstructs.jl:34
 [3] macro expansion at ./threadingconstructs.jl:93 [inlined]
 [4] thread_samples(::Int64) at /Users/mfairley/Projects/AdaptiveSurveillance/test/test_threaded_rng.jl:10
 [5] top-level scope at /Users/mfairley/Projects/AdaptiveSurveillance/test/test_threaded_rng.jl:15
 [6] include(::Function, ::Module, ::String) at ./Base.jl:380
 [7] include(::Module, ::String) at ./Base.jl:368
 [8] exec_options(::Base.JLOptions) at ./client.jl:296
 [9] _start() at ./client.jl:506
in expression starting at /Users/mfairley/Projects/AdaptiveSurveillance/test/test_threaded_rng.jl:15

EDIT:
Is this the best way?

function thread_samples(K)
    rng = [MersenneTwister(i) for i = 1:Threads.nthreads()]
    samples = zeros(Int64, K)
    Threads.@threads for k = 1:K
        samples[k] = rand(rng[Threads.threadid()], 1:10)
    end
end

Yes.
The problem is that rngs aren’t thread-safe. When you sample random numbers, you’re mutating the RNG’s state.
Using one RNG per thread is best.

3 Likes