Home > Software engineering >  Data race guarded by if (false)... what does the standard say?
Data race guarded by if (false)... what does the standard say?

Time:06-10

Consider the following situation

// Global
int x = 0; // not atomic

// Thread 1
x = 1;

// Thread 2
if (false)
    x = 2;

Does this constitute a data race according to the standard? [intro.races] says:

Two expression evaluations conflict if one of them modifies a memory location (4.4) and the other one reads or modifies the same memory location.

The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.

Is it safe from a language-lawyer perspective, because the program can never be allowed to perform the "expression evaluation" x = 2;?

From a technical standpoint, what if some weird, stupid compiler decided to perform a speculative execution of this write, rolling it back after checking the actual condition?

What inspired this question is the fact that (at least in Standard 11), the following program was allowed to have its result depend entirely on reordering/speculative execution:

// Thread 1:
r1 = y.load(std::memory_order_relaxed);
if (r1 == 42) x.store(r1, std::memory_order_relaxed);
// Thread 2:
r2 = x.load(std::memory_order_relaxed);
if (r2 == 42) y.store(42, std::memory_order_relaxed);
// This is allowed to result in r1==r2==42 in c  11

(compare https://en.cppreference.com/w/cpp/atomic/memory_order)

CodePudding user response:

The key term is "expression evaluation". Take the very simple example:

int a = 0;
for (int i = 0; i != 10;   i) 
     a;

There's one expression a , but 10 evaluations. These are all ordered: the 5th evaluation happens-before the 6th evaluation. And the evaluations of a are interleaved with the evaluations of i!=10.

So, in

int a = 0;
for (int i = 0; i != 0;   i) 
     a;

there are 0 evaluations. And by a trivial rewrite, that gets us

int a = 0;
if (false)
     a;

Now, if there are 10 evaluations of a, we need to worry for all 10 evaluations if they race with another thread (in more complex cases, the answer might vary - say if you start a thread when a==5). But if there are no evaluations at all of a, then there's clearly no racing evaluation.

CodePudding user response:

Does this constitute a data race according to the standard?

No, data races are concerned with access to storage locations in expressions which are actually evaluated as your quote states. In if (false) x = 2; the expression x = 2; is never evaluated. Hence it doesn't matter at all to determining the presence of data races.

Is it safe from a language-lawyer perspective, because the program can never be allowed to perform the "expression evaluation" x = 2;?

Yes.

From a technical standpoint, what if some weird, stupid compiler decided to perform a speculative execution of this write, rolling it back after checking the actual condition?

It is not allowed to do that if it could affect the observable behavior of the program. Otherwise it may do that, but it is impossible to observe the difference.

What inspired this question is the fact that (at least in Standard 11), the following program was allowed to have its result depend entirely on reordering/speculative execution:

That's a completely different situation. This program also doesn't have any data races, since the only variables that are accessed in both threads are atomics, which can never have data races. It merely has potentially multiple valid results, meaning a race condition. A data race would always imply undefined behavior, not merely unspecified behavior.

Also the out-of-thin-air issue appears only as a result of the circular dependence of the accesses between multiple atomics. In your initial example there is only one variable, non-atomic and without any such circular dependence.

  • Related