Data Races

The updated multithreading docs for v"1.5" says in section “Data-race freedom”:

Additionally, Julia is not memory safe in the presence of a data race. Be very careful about reading a global variable (or closure variable) if another thread might write to it! Instead, always use the lock pattern above when changing any data (such as assigning to a global) visible to multiple threads.

Thread 1:
global b = false
global a = rand()
global b = true

Thread 2:
while !b; end
bad(a) # it is NOT safe to access `a` here!

Thread 3:
while !@isdefined(a); end
use(a) # it is NOT safe to access `a` here

It is not exactly clear to me, what this example tries to show. (two separated issues, or a single one?)

Are the following true?

  • Removing thread 3 resolves the data race.
  • If both use(a) and bad(a) only reads the value of a then thread 2 is safe.
  • I guess that @isdefined returns true before the random value is assigned to a, so removing thread 2 does not resolve the race.

I plan to clarify the docs, but not sure if my understanding is correct. Thank you for helping!

No. Thread 1 is the only writer and the two reader threads 2 and 3 each races with 1.

No. And this is exactly the point of the examples.

And no. @isdefined will not do that, but the assignment may happen before the value is initialized.

1 Like

Wow, thanks!

Please clarify this a bit more. What means “the value is initialized”?

Here is my current understanding:

There may be a time when a is defined but uninitialized, so thread 3 is not safe. What about thread 2? May b be true before a gets initialized because of optimizations?

Yes. But not really because of optimization. Unless you also include hardware behavior in the optimization.

You seems to be thinking things in the sequential consistent model. That is not the case. In general, unless there’s explicit synchronized, you cannot expect things happens in one order for one thread to happen in the same order on another.

I think that what I assumed was not more than processor consistency, meaning that:

the order in which other processors see the writes from any individual processor is the same as the order they were issued. (wp)

And although I am not familiar with the topic, it seems that modern processors guarantee something like this. E.g in x86-TSO:

Screenshot from 2020-08-03 22-09-38

If that is true, then I think that thread 2 is safe in the current form of the example, when thread 1 changes the value of b only once. Of course “locking” this way is still not a good idea, but the question is interesting.

A similar, and maybe more realistic example that comes to my mind is a mutable struct stored in a shared variable, which one wants to change swiftly by creating an another instance and locking only while changing the reference. Is that safe, or the writes of the struct creation may arrive too late?

No, it is only the case for hardware on x86. Nothing else have that. And then of course on x86, the compiler can reorder things so you still can’t rely on it anywhere.

In general, there’s no guarantee when you write a data race. If you have a race the access have to be explicitly ordered. (Not yet supported in Julia).

1 Like

Thank you for your insights! I have created a small PR with the hope that it helps others to understand this.