How to "lock" variables in @threads?

Viewed 85

I am running a multithreaded parameter tuning for some ML algorithms. I can avoid allocation in the input using CartesianIndices, but how can I avoid allocation in the output ? That is, how do I lock, in the example below, the variables bestError, bestPar1 and bestPar2 to avoid race conditions ?

par1 = [1,2]
par2 = [0.1,0.2,0.3]

doMyStuff(par1,par2) = abs(0-(-par1^3+par1^2+par1+par2^3-par2^2+par2+10))

# Version 1: Preallocation, computation and comparison
# Step A : preallocation
errorMatrix   = fill(Inf64,length(par1),length(par2))
# Step B: computation (multi-threaded)
function tuneParameters!(errorMatrix,par1,par2)
    Threads.@threads for ij in CartesianIndices((length(par1),length(par2)))
           (p1i, p2j)    = par1[Tuple(ij)[1]], par2[Tuple(ij)[2]]
           errorMatrix[Tuple(ij)...] = doMyStuff(p1i,p2j)
    end
    return errorMatrix
end
tuneParameters!(errorMatrix,par1,par2)
# Step C: comparison (single-thread)
bestError          = minimum(errorMatrix)
bestPar1, bestPar2 = par1[Tuple(argmin(errorMatrix))[1]], par2[Tuple(argmin(errorMatrix))[2]]

# Version 2: computation and comparison inside the multi-threaded loop
function tuneParametersB(par1,par2)
    # Step A : initialisation
    bestError = Inf64
    bestPar1  = nothing
    bestPar2  = nothing
    # Step B: computation and comparison
    Threads.@threads for ij in CartesianIndices((length(par1),length(par2)))
           (p1i, p2j)    = par1[Tuple(ij)[1]], par2[Tuple(ij)[2]]
           attempt       = doMyStuff(p1i,p2j)
           begin
               # lock(bestError,bestPar1,bestPar2) # this doesn't work
               lock(bestError) # neither does this
               lock(bestPar1)
               lock(bestPar2)
               try
                   if(attempt < bestError) 
                       bestError = attempt
                       bestPar1  = p1i
                       bestPar2  = p2j
                   end
               finally
                   #unlock(bestError,bestPar1,bestPar2)
                   unlock(bestError)
                   unlock(bestPar1)
                   unlock(bestPar2)
               end
           end
    end
    return (bestError,bestPar1, bestPar2)
end

bestError , bestPar1, bestPar2 = tuneParametersB(par1,par2) # MethodError: no method matching lock(::Float64)

EDIT:

I found this at least not to crash. Can you confirm is a valid approach ?

function tuneParametersD(par1,par2)
    # Step A : initialisation
    bestError = Inf64
    bestPar1  = nothing
    bestPar2  = nothing
    compLock  = ReentrantLock()
    
    # Step B: computation and comparision
    Threads.@threads for ij in CartesianIndices((length(par1),length(par2)))
           (p1i, p2j)    = par1[Tuple(ij)[1]], par2[Tuple(ij)[2]]
           attempt       = doMyStuff(p1i,p2j)
           begin
               lock(compLock)
               try
                   if(attempt < bestError) 
                       bestError = attempt
                       bestPar1  = p1i
                       bestPar2  = p2j
                   end
               finally
                   unlock(compLock)
               end
           end
    end
    return (bestError,bestPar1, bestPar2)
end

Unfortunately the Documentation assume several prior knowledge, e.g. it doesn't explain what a lock, either as a concept or as the parameter of the lock function, is... I assumed it was a variable, but it seem it isn't..

0 Answers
Related