I wrote my module PDFPs.jl to implement Precision Decimal Floating Point object. I was very very careful to make my code purely functional with pure functions. So I thought that my code is thread safe by default.
But alas, i reaised that it depends heavily on one “Global Variable” called DEFAULT_PRECISION
module PDFPs
# Use the Ref hack to speed up DEFAULT_PRECISION
# to get the value use DEFAULT_PRECISION[]
# to set the value use DEFAULT_PRECISION[] = newvalue
const DEFAULT_PRECISION = Ref(16)
@inline function PDFP_getDefaultPrecision()
DEFAULT_PRECISION[]
end
@inline function PDFP_setDefaultPrecision(prec::Int)
global DEFAULT_PRECISION[] = prec < 0 ? 0 : prec
end
end
It took me a while to realised that this is totally unsafe as when I have multiple threads running, each thread could be changing the DEFAULT_PRECISION and interferring with each other. What I need is a local copy of DEFAULT_PRECISION for each thread. So I came up with this.
module PDFPs
const DEFAULT_PRECISION = Ref(16)
# Thread id for the main thread is always 1, so put in the local copy
LocalCopy_DefaultPrecision = Dict{Int64,Int64}(1=>DEFAULT_PRECISION[])
Mutex_LocalCopy_DefaultPrecision = Base.Threads.SpinLock()
function PDFP_getDefaultPrecision()
thread_id = Base.Threads.threadid()
Base.Threads.lock(Mutex_LocalCopy_DefaultPrecision)
if ! haskey(LocalCopy_DefaultPrecision,thread_id)
LocalCopy_DefaultPrecision[thread_id] = DEFAULT_PRECISION[]
end
result = LocalCopy_DefaultPrecision[thread_id]
Base.Threads.unlock(Mutex_LocalCopy_DefaultPrecision)
return result
end
function PDFP_setDefaultPrecision(prec::Int)
thread_id = Base.Threads.threadid()
Base.Threads.lock(Mutex_LocalCopy_DefaultPrecision)
LocalCopy_DefaultPrecision[thread_id] = prec < 0 ? 0 : prec
Base.Threads.unlock(Mutex_LocalCopy_DefaultPrecision)
end
# Next we need to replace all DefaultPrecision[] with
# PDFP_getDefaultPrecision()
#
# And replace all DefaultPrecision[]=<number> with
# PDFP_setDefaultPrecision(<number>)
end
Would my new code make my module Thread safe for Julia 1.2 and 1.3?
Do you think it is possible to have a magical MACRO called @localtoeachthread so that I can simple do this instead?
module PDFPs
# Use the Ref hack to speed up DEFAULT_PRECISION
# to get the value use DEFAULT_PRECISION[]
# to set the value use DEFAULT_PRECISION[] = newvalue
@localtoeachthread const DEFAULT_PRECISION = Ref(16)
@inline function PDFP_getDefaultPrecision()
DEFAULT_PRECISION[]
end
@inline function PDFP_setDefaultPrecision(prec::Int)
global DEFAULT_PRECISION[] = prec < 0 ? 0 : prec
end
end
But what if the number of threads changes during the execution of the program? My solution would work even if the number of threads changes dynamically.
Not sure what the usage model is, but I feel like you should create a “Context” object that has the default precision, then all your methods would take this Context as a parameter.
My worry is, function A sets the default precision does some operations, calls function B which changes the default precision, does some operations, returns, function A continues to run but the default precision has changed. It would not be obvious looking at function A that the precision HAS changed.
It’s a recipe for hidden bugs, especially if the first pass of function B doesn’t change the default precision, but at some later point code is updated to change the default precision. If you don’t restore the precision when B exits, function A is going to operate differently.
This all assumes the default precision gets changed often, by your question it sounds like it does. The other option is you require the client set it once and it doesn’t get changed…
Changing the constant value of a default defeats a bit its purpose in the first place i.e. to be a constant value with which some other local mutable gets initialized.
My worry is, function A sets the default precision does some operations, calls function B which changes the default precision, does some operations, returns, function A continues to run but the default precision has changed. It would not be obvious looking at function A that the precision HAS changed.
That is not an issue as only humans on the Julia REPL can call PDFP_setDefaultPrecision(). Functions within PDFPs.jl does not change the DEFAULT_PRECISION but rather uses it as a constant.
My real worry is that julia code from other people uses my module PDFPs.jl and calls PDFP_setDefaultPrecision() from multiple threads.
If you are referring to other people’s code, then there is nothing I can do about it as I cannot stop other coders from writing stupid codes when using PDFP_setDefaultPrecision().
Changing the constant value of a default defeats a bit its purpose in the first place i.e. to be a constant value with which some other local mutable gets initialized.