# This file is a part of Julia. License is MIT: https://julialang.org/license using Base.Test using Base.Threads # threading constructs # parallel loop with parallel atomic addition function threaded_loop(a, r, x) @threads for i in r a[i] = 1 + atomic_add!(x, 1) end end function test_threaded_loop_and_atomic_add() x = Atomic() a = zeros(Int,10000) threaded_loop(a,1:10000,x) found = zeros(Bool,10000) was_inorder = true for i=1:length(a) was_inorder &= a[i]==i found[a[i]] = true end @test x[] == 10000 # Next test checks that all loop iterations ran, # and were unique (via pigeon-hole principle). @test findfirst(found,false) == 0 if was_inorder println(STDERR, "Warning: threaded loop executed in order") end end test_threaded_loop_and_atomic_add() # Helper for test_threaded_atomic_minmax that verifies sequential consistency. function check_minmax_consistency{T}(old::Array{T,1}, m::T, start::T, o::Base.Ordering) for v in old if v != start # Check that atomic op that installed v reported consistent old value. @test Base.lt(o, old[v-m+1], v) end end end function test_threaded_atomic_minmax{T}(m::T,n::T) mid = m + (n-m)>>1 x = Atomic{T}(mid) y = Atomic{T}(mid) oldx = Array{T}(n-m+1) oldy = Array{T}(n-m+1) @threads for i = m:n oldx[i-m+1] = atomic_min!(x, T(i)) oldy[i-m+1] = atomic_max!(y, T(i)) end @test x[] == m @test y[] == n check_minmax_consistency(oldy,m,mid,Base.Forward) check_minmax_consistency(oldx,m,mid,Base.Reverse) end # The ranges below verify that the correct signed/unsigned comparison is used. test_threaded_atomic_minmax(Int16(-5000),Int16(5000)) test_threaded_atomic_minmax(UInt16(27000),UInt16(37000)) function threaded_add_locked{LockT}(::Type{LockT}, x, n) critical = LockT() @threads for i = 1:n @test lock(critical) === nothing @test islocked(critical) x = x + 1 @test unlock(critical) === nothing end @test !islocked(critical) nentered = 0 nfailed = Atomic() @threads for i = 1:n if trylock(critical) @test islocked(critical) nentered += 1 @test unlock(critical) === nothing else atomic_add!(nfailed, 1) end end @test 0 < nentered <= n @test nentered + nfailed[] == n @test !islocked(critical) return x end @test threaded_add_locked(SpinLock, 0, 10000) == 10000 @test threaded_add_locked(RecursiveSpinLock, 0, 10000) == 10000 @test threaded_add_locked(Mutex, 0, 10000) == 10000 # Check if the recursive lock can be locked and unlocked correctly. let critical = RecursiveSpinLock() @test !islocked(critical) @test_throws AssertionError unlock(critical) @test lock(critical) === nothing @test islocked(critical) @test lock(critical) === nothing @test trylock(critical) == true @test islocked(critical) @test unlock(critical) === nothing @test islocked(critical) @test unlock(critical) === nothing @test islocked(critical) @test unlock(critical) === nothing @test !islocked(critical) @test_throws AssertionError unlock(critical) @test trylock(critical) == true @test islocked(critical) @test unlock(critical) === nothing @test !islocked(critical) @test_throws AssertionError unlock(critical) @test !islocked(critical) end # Make sure doing a GC while holding a lock doesn't cause dead lock # PR 14190. (This is only meaningful for threading) function threaded_gc_locked{LockT}(::Type{LockT}) critical = LockT() @threads for i = 1:20 @test lock(critical) === nothing @test islocked(critical) gc(false) @test unlock(critical) === nothing end @test !islocked(critical) end threaded_gc_locked(SpinLock) threaded_gc_locked(Threads.RecursiveSpinLock) threaded_gc_locked(Mutex) # Issue 14726 # Make sure that eval'ing in a different module doesn't mess up other threads orig_curmodule14726 = current_module() main_var14726 = 1 module M14726 module_var14726 = 1 end @threads for i in 1:100 for j in 1:100 @eval M14726 module_var14726 = $j end end @test isdefined(:orig_curmodule14726) @test isdefined(:main_var14726) @test current_module() == orig_curmodule14726 @threads for i in 1:100 # Make sure current module is not null. # The @test might not be particularly meaningful currently since the # thread infrastructures swallows the error. (Same below) @test current_module() == orig_curmodule14726 end module M14726_2 using Base.Test using Base.Threads @threads for i in 1:100 # Make sure current module is the same as the one on the thread that # pushes the work onto the threads. # The @test might not be particularly meaningful currently since the # thread infrastructures swallows the error. (See also above) @test current_module() == M14726_2 end end # Ensure only LLVM-supported types can be atomic @test_throws TypeError Atomic{Bool} @test_throws TypeError Atomic{BigInt} @test_throws TypeError Atomic{Complex128} # Test atomic memory ordering with load/store mutable struct CommBuf var1::Atomic{Int} var2::Atomic{Int} correct_write::Bool correct_read::Bool CommBuf() = new(Atomic{Int}(0), Atomic{Int}(0), false, false) end function test_atomic_write(commbuf::CommBuf, n::Int) for i in 1:n # The atomic stores guarantee that var1 >= var2 commbuf.var1[] = i commbuf.var2[] = i end commbuf.correct_write = true end function test_atomic_read(commbuf::CommBuf, n::Int) correct = true while true # load var2 before var1 var2 = commbuf.var2[] var1 = commbuf.var1[] correct &= var1 >= var2 var1 == n && break # Temporary solution before we have gc transition support in codegen. ccall(:jl_gc_safepoint, Void, ()) end commbuf.correct_read = correct end function test_atomic() commbuf = CommBuf() count = 1_000_000 @threads for i in 1:2 if i==1 test_atomic_write(commbuf, count) else test_atomic_read(commbuf, count) end end @test commbuf.correct_write == true @test commbuf.correct_read == true end test_atomic() # Test ordering with fences using Peterson's algorithm # Example adapted from mutable struct Peterson # State for Peterson's algorithm flag::Vector{Atomic{Int}} turn::Atomic{Int} # Collision detection critical::Vector{Atomic{Int}} correct::Vector{Bool} Peterson() = new([Atomic{Int}(0), Atomic{Int}(0)], Atomic{Int}(0), [Atomic{Int}(0), Atomic{Int}(0)], [false, false]) end function test_fence(p::Peterson, id::Int, n::Int) @assert id == mod1(id,2) correct = true otherid = mod1(id+1,2) for i in 1:n p.flag[id][] = 1 p.turn[] = otherid atomic_fence() while p.flag[otherid][] != 0 && p.turn[] == otherid # busy wait # Temporary solution before we have gc transition support in codegen. ccall(:jl_gc_safepoint, Void, ()) end # critical section p.critical[id][] = 1 correct &= p.critical[otherid][] == 0 p.critical[id][] = 0 # end of critical section p.flag[id][] = 0 end p.correct[id] = correct end function test_fence() commbuf = Peterson() count = 1_000_000 @threads for i in 1:2 test_fence(commbuf, i, count) end @test commbuf.correct[1] == true @test commbuf.correct[2] == true end test_fence() # Test load / store with various types let atomic_types = [Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128, Float16, Float32, Float64] # Temporarily omit 128-bit types on 32bit x86 # 128-bit atomics do not exist on AArch32. # And we don't support them yet on power, because they are lowered # to `__sync_lock_test_and_set_16`. if Sys.ARCH === :i686 || startswith(string(Sys.ARCH), "arm") || Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le filter!(T -> sizeof(T)<=8, atomic_types) end for T in atomic_types var = Atomic{T}() var[] = 42 @test var[] === T(42) old = atomic_xchg!(var, T(13)) @test old === T(42) @test var[] === T(13) old = atomic_cas!(var, T(13), T(14)) # this will succeed @test old === T(13) @test var[] === T(14) old = atomic_cas!(var, T(13), T(15)) # this will fail @test old === T(14) @test var[] === T(14) end end # Test atomic_cas! and atomic_xchg! function test_atomic_cas!{T}(var::Atomic{T}, range::StepRange{Int,Int}) for i in range while true old = atomic_cas!(var, T(i-1), T(i)) old == T(i-1) && break # Temporary solution before we have gc transition support in codegen. ccall(:jl_gc_safepoint, Void, ()) end end end for T in (Int32, Int64, Float32, Float64) var = Atomic{T}() nloops = 1000 di = nthreads() @threads for i in 1:di test_atomic_cas!(var, i:di:nloops) end @test var[] === T(nloops) end function test_atomic_xchg!{T}(var::Atomic{T}, i::Int, accum::Atomic{Int}) old = atomic_xchg!(var, T(i)) atomic_add!(accum, Int(old)) end for T in (Int32, Int64, Float32, Float64) accum = Atomic{Int}() var = Atomic{T}() nloops = 1000 @threads for i in 1:nloops test_atomic_xchg!(var, i, accum) end @test accum[] + Int(var[]) === sum(0:nloops) end function test_atomic_float{T}(varadd::Atomic{T}, varmax::Atomic{T}, varmin::Atomic{T}, i::Int) atomic_add!(varadd, T(i)) atomic_max!(varmax, T(i)) atomic_min!(varmin, T(i)) end for T in (Int32, Int64, Float32, Float64) varadd = Atomic{T}() varmax = Atomic{T}() varmin = Atomic{T}() nloops = 1000 @threads for i in 1:nloops test_atomic_float(varadd, varmax, varmin, i) end @test varadd[] === T(sum(1:nloops)) @test varmax[] === T(maximum(1:nloops)) @test varmin[] === T(0) end for period in (0.06, Dates.Millisecond(60)) let async = Base.AsyncCondition(), t c = Condition() task = schedule(Task(function() notify(c) wait(c) t = Timer(period) wait(t) ccall(:uv_async_send, Void, (Ptr{Void},), async) ccall(:uv_async_send, Void, (Ptr{Void},), async) wait(c) sleep(period) ccall(:uv_async_send, Void, (Ptr{Void},), async) ccall(:uv_async_send, Void, (Ptr{Void},), async) end)) wait(c) notify(c) delay1 = @elapsed wait(async) notify(c) delay2 = @elapsed wait(async) @test istaskdone(task) @test delay1 > 0.05 @test delay2 > 0.05 @test isopen(async) @test !isopen(t) close(t) close(async) @test_throws EOFError wait(async) @test !isopen(async) @test_throws EOFError wait(t) @test_throws EOFError wait(async) end end complex_cfunction = function(a) s = zero(eltype(a)) @inbounds @simd for i in a s += muladd(a[i], a[i], -2) end return s end function test_thread_cfunction() @threads for i in 1:1000 # Make sure this is not inferrable # and a runtime call to `jl_function_ptr` will be created ccall(:jl_function_ptr, Ptr{Void}, (Any, Any, Any), complex_cfunction, Float64, Tuple{Ref{Vector{Float64}}}) end end test_thread_cfunction() # Compare the two ways of checking if threading is enabled. # `jl_tls_states` should only be defined on non-threading build. if ccall(:jl_threading_enabled, Cint, ()) == 0 @test nthreads() == 1 cglobal(:jl_tls_states) != C_NULL else @test_throws ErrorException cglobal(:jl_tls_states) end function test_thread_range() a = zeros(Int, nthreads()) @threads for i in 1:threadid() a[i] = 1 end for i in 1:threadid() @test a[i] == 1 end for i in (threadid() + 1):nthreads() @test a[i] == 0 end end test_thread_range() # Thread safety of `jl_load_and_lookup`. function test_load_and_lookup_18020(n) @threads for i in 1:n try ccall(:jl_load_and_lookup, Ptr{Void}, (Cstring, Cstring, Ref{Ptr{Void}}), "$i", :f, C_NULL) end end end test_load_and_lookup_18020(10000) # Nested threaded loops # This may not be efficient/fully supported but should work without crashing..... function test_nested_loops() a = zeros(Int, 100, 100) @threads for i in 1:100 @threads for j in 1:100 a[j, i] = i + j end end for i in 1:100 for j in 1:100 @test a[j, i] == i + j end end end test_nested_loops()