Race condition with local variable in @threads for loop

Hi, im trying to figure why this code throw and AssertionError inside the most inner loop. My current understanding is that julia will split the top loop into a number of pieces and spawn each piece to a separate thread. So, I just interpret the whole code inside the top loop as always running in the context of a given task.

Why then is the second assertion triggered?
Does julia spawn the inner for loop too?
If so, How can I avoid it?

# test.jl
import Base.Threads: @threads, nthreads

function test()
    @threads :static for i in 1:100

        d = Dict(:a => Dict())
        a = d[:a]
        @assert a === d[:a]

        # Line 12: This throw an AssertionError
        for j in 1:100; @assert a === d[:a] end 
    end
    a = [] # If this line is commented the problem disappears 
end

println("Running test (-t$(nthreads()))")
for i = 1:500; test() end
$ julia -t4 test.jl
ERROR: LoadError: TaskFailedException:
AssertionError: a === d[:a]
Stacktrace:
 [1] macro expansion at /Users/Pereiro/Documents/dev/threads_issue/test.jl:12 [inlined]
 [2] (::var"#41#threadsfor_fun#1"{UnitRange{Int64}})(::Bool) at ./threadingconstructs.jl:81
 [3] (::var"#41#threadsfor_fun#1"{UnitRange{Int64}})() at ./threadingconstructs.jl:48
[...]

Something strange that makes me think I’m missing something is that if I comment the a=[] line, the error doesn’t happen.

My version info

$ julia -e "using InteractiveUtils; versioninfo()"
Julia Version 1.5.2
Commit 539f3ce943 (2020-09-23 23:17 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin18.7.0)
  CPU: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
  JULIA_NUM_THREADS = 4

Any help will be appreciated :slight_smile:

Thanks!

The last assignment makes the variable a have a scope outside the threads section.

3 Likes

a has to be strictly local to @threads for. Your code seems to make it an existing local variable (see Kristoffer’s comment). Then threads interfere with each other. Make it explicitly local and it runs.

function test()
    @threads for i in 1:100
        ...
        local a = d[:a]
        ...
    end
    a = [] # ...
end

PS: edited

1 Like

Uff, I wouldn’t figured out by myself, thanks! I will change the title for improve findability, any suggestion?

1 Like

ok, I have clarified my response.

Perhaps you could call it “Race condition with local variable in @threads for”

1 Like

FYI @floop ex for from FLoops.jl can be used instead of @threads for and it’d detect this error. It also shows which variable is causing it:

@floop ex for workarounds common pitfalls of @threads for like this one, provides a rich reduction API, and supports multiple backends including GPU, with FoldsCUDA.jl.

7 Likes