Confusing documentation on "Data-race freedom"

So I’m brand new at multi-threading and I basically want to lock access to a global variable to all other threads while one task writes to it. I’m looking at the documentation https://docs.julialang.org/en/v1/manual/multi-threading/ and I can’t make heads or tail as to what “a” is supposed to mean in

lock(a) do
     use(a)
end

If I have a variable MyGlobal and I’m trying to update a field:

function update_global_field(x)
    global MyGlobal
    MyGlobal.field = x
end

How do I get a task to write to this field and other tasks to read from this field without race conditions? I tried

lock(MyGlobal) do
     update_global_field(x)
end

but that didn’t work.

1 Like

You can’t lock arbitary things.
You can only lock things that implement the lock method.
Which predominantly is subtypes of AbstractLock
So do a

using Base.Threads
const my_global_lock = ReentrantLock()

lock(my_global_lock) do
    update_global_field(x)
end
1 Like

So how do I use this lock to stop other functions from reading this global variable until I’m done writing to it? What did “my_global_lock” actually lock?

It is a lock. It didn’t lock anything. It is a lock, it is the thing that is locked.
lock(my_global_lock) locked my_global_lock
If some other thread tried to do lock(my_global_lock) then it would be blocked.

You often will want to pair a lock with a variable (or sometimes a set of variables, or sometimes a part of a value like a column in an array or even entry with a given hash of its index or …).

So the basic way you would to is:

using Base.Threads
const my_global_lock = ReentrantLock()
my_global_value = nothing

function set_value!(x)
    lock(my_global_lock) do
        global my_global_value
        my_global_value = x
    end
end

function get_value(x)
    return lock(my_global_lock) do
        return my_global_value
    end
end

then if you only access my_global_value
via set_value! and get_value and never directly then you are safe.

Though realistically you want to actually avoid global mutable state;
and avoid situations where you need to share memory between threads, as much as possible.
This kind of thing is fine maybe if its a config, that is rarely read or set,
but a problem if its in your hot-loop.
Since locking and unlocking is expensive.

3 Likes

Oooooooh! That makes sense!

I’m very familiar with trying to avoid a global mutable state, so it is only being updated once a day. It’s just that there’s a lot of background work that happens while updating so I don’t want to hold up other api calls.

I actually start up another task in the backround that runs concurrently, but I DO need the other calls to wait for the split-second it takes to transfer these results to the global state. Normally this isn’t an issue (the “dangerous” update code is very short and likely to happen between calls). I just want to be SURE that it’s safe so I need to use locks.

1 Like

yep that sounds like a great use for locking.