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)?
// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2
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.
The memory location x
is ... written to by thread 2
Is it really? Why do you say so?
If y
is 0 then x
is not written to by thread 2. And y
starts out 0. Similarly, x
cannot be non-zero unless somehow y
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 code if (x) y = 1;
to int 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 be y = 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 assignment y = y
that makes it safe to execute unsequenced with respect to a read of y
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.
There has to be a total ordering of the writes, because of the fact that no thread can write to the variable x
or y
until some other thread has first written a 1
to either variable. In other words you have basically three different scenarios:
- thread 1 gets to write to
y
because x
was written to at some previous point before the if
statement, and then if thread 2 comes later, it writes to x
the same value of 1
, and doesn't change it's previous value of 1
.
- thread 2 gets to write to
x
because y
was changed at some point before the if
statement, and then thread 1
will write to y
if it comes later the same value of 1
.
- If there are only two threads, then the
if
statements are jumped over because x
and y
remain 0.
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.