I was reading Bjarne Stroustrup's C++11 FAQ and I'm having trouble understanding an example in the memory model section.
He gives the following code snippet:
// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2
The FAQ says there is not a data race here. I don't understand. The memory location x
is read by thread 1 and written to by thread 2 without any synchronization (and the same goes for y
). That's two accesses, one of which is a write. Isn't that the definition of a data race?
Further, it says that "every current C++ compiler (that I know of) gives the one right answer." What is this one right answer? Couldn't the answer vary depending on whether one thread's comparison happens before or after the other thread's write (or if the other thread's write is even visible to the reading thread)?
Neither of the writes occurs, so there is no race. Both x and y remain zero.
(This is talking about the problem of phantom writes. Suppose one thread speculatively did the write before checking the condition, then attempted to correct things after. That would break the other thread, so it isn't allowed.)
Memory model set the supportable size of code and data areas.before comparing linking source code,we need to specify the memory model that is he can set the size limitsthe data and code.
There has to be a total ordering of the writes, because of the fact that no thread can write to the variable
x
ory
until some other thread has first written a1
to either variable. In other words you have basically three different scenarios:y
becausex
was written to at some previous point before theif
statement, and then if thread 2 comes later, it writes tox
the same value of1
, and doesn't change it's previous value of1
.x
becausey
was changed at some point before theif
statement, and then thread1
will write toy
if it comes later the same value of1
.if
statements are jumped over becausex
andy
remain 0.Since neither x nor y is true, the other won't be set to true either. No matter the order the instructions are executed, the (correct) result is always x remains 0, y remains 0.
Is it really? Why do you say so?
If
y
is 0 thenx
is not written to by thread 2. Andy
starts out 0. Similarly,x
cannot be non-zero unless somehowy
is non-zero "before" thread 1 runs, and that cannot happen. The general point here is that conditional writes that don't execute don't cause a data race.This is a non-trivial fact of the memory model, though, because a compiler that is not aware of threading would be permitted (assuming
y
is not volatile) to transform the codeif (x) y = 1;
toint tmp = y; y = 1; if (!x) y = tmp;
. Then there would be a data race. I can't imagine why it would want to do that exact transformation, but that doesn't matter, the point is that optimizers for non-threaded environments can do things that would violate the threaded memory model. So when Stroustrup says that every compiler he knows of gives the right answer (right under C++11's threading model, that is), that's a non-trivial statement about the readiness of those compilers for C++11 threading.A more realistic transformation of
if (x) y = 1
would bey = x ? 1 : y;
. I believe that this would cause a data race in your example, and that there is no special treatment in the standard for the assignmenty = y
that makes it safe to execute unsequenced with respect to a read ofy
in another thread. You might find it hard to imagine hardware on which it doesn't work, and anyway I may be wrong, which is why I used a different example above that's less realistic but has a blatant data race.