ReentrantLock: generate once, or every time before `for`

I learnt about ReentrantLock in JuMP’s doc.

function a_correct_way_to_build_with_multithreading()
           model = Model()
           @variable(model, x[1:10])
           my_lock = Threads.ReentrantLock()
           Threads.@threads for i in 1:10
               con = @build_constraint(x[i] <= i)
               Threads.lock(my_lock) do
                   add_constraint(model, con)
               end
           end
           return model
       end

This example reveals some idea, but it is not thorough. Therefore I have a question.

  • is this line my_lock = Threads.ReentrantLock() a one-off operation, or should we write it every time immediately before Threads.@threads for i in 1:10?

Take a simpler analogous example. This is a one-off case

container = Vector{Int}(undef, 3) # one-off is enough
while true
    # here we don't need to allocate container once more
    # we also don't need to reset some state, because they are irrelevant
    Threads.@threads for i in 1:3
        container[i] = i
    end
end

This is a case we must execute every time before for

flag = falses(3) # allocate
flag .= false # reset the state
while true
    flag .= false # we must reset the state
    Threads.@threads for i in 1:3
        if flag[i] == false
            flag[i] = true
        else
            error()
        end
    end
end

It appears that we only need to acquire my_lock one-off.
Here is a simple test I don’t know whether it is appropriate

my_lock = Threads.ReentrantLock(); # acquire it only once
std_ans = sum(1:1000000);
for i = 1:100 # test 100 times
    s = 0
    Threads.@threads for i = 1:1000000
        Threads.lock(my_lock) do # reuse it multiple times
            s += i
        end
    end
    s == std_ans || error()
end
# No ERROR can be seen, my might infer `my_lock` works properly

Ive moved this out of the optimization category because it isnt specific to JuMP. I suggest you look up the Julia documentation for threading and locks, or any computer science text. This isnt a topic specific to Julia.

This is correct. A lock is just a struct. You may create it as global const, or just before the loop. The only requirement is that all the tasks that use it for synchronization uses the same instance. Creating a lock is a very lightweight operation (it’s just a struct, no complicated setup), so for clarity you should perhaps create it just prior to where you use it. However, it’s a mutable struct, so memory is allocated for it.

1 Like