5

Assuming that x is a shared inter-thread variable and func is always returning 0, does the below code contain a data race in terms of C11 and C++11? Please assume that x is written in two different threads, always with a proper lock, except the switch statement below.

int x; // global variable

...

int y; // local variable

...

switch (func())
{
  case 1:
  {
    x = 0;
    y = 1;
    break;
  }
  case 2:
  {
    x = 0;
    y = 2;
    break;
  }
  case 3:
  default:
  {
    y = 3;
    break;
  }
}

There is a note in the standard (both C11 and C++11) that precludes compiler transformations that introduces a data race to the code. Is the compiler allowed to transform the code like below? The code below certainly contains a data race but the question is if the compiler has introduced it or if it already was in the original code. There was an unprotected access to a shared variable, although unreachable.

int x; // global variable

...

int y; // local variable

...

temp = x;
x = 0;
switch (func())
{
  case 1:
  {
    y = 1;
    break;
  }
  case 2:
  {
    y = 2;
    break;
  }
  case 3:
  default:
  {
    x = temp;
    y = 3;
    break;
  }
}
mrn
  • 959
  • 5
  • 15
  • Without any synchronization mechanisms expect data races, yes. You should rather have a `std::atomic x;`. – πάντα ῥεῖ Apr 17 '16 at 21:57
  • It is not just a data-race. C does not have a concept of concurrent processes. – too honest for this site Apr 17 '16 at 22:07
  • _@mm_ Your sample doesn't show any concurrent execution. Could you add that please to enable giving you concise answers. – πάντα ῥεῖ Apr 17 '16 at 22:14
  • This is an example of where C and C++ are utterly different languages. – Yakk - Adam Nevraumont Apr 17 '16 at 22:22
  • Data races only occur in case of writes, which is also your case. So, in principle, your program could cause data inconsistencies. – nbro Apr 17 '16 at 22:24
  • @WhiZTiM Do you mean a dependency in terms of the standard? One could come accross this type of transformations before C11 and C++11 appeared, so do you mean that with the definition of dependency, the new standards preclude such transformations? – mrn Apr 17 '16 at 22:24
  • @πάντα ῥεῖ I've added information on how x is accessed from different threads. – mrn Apr 17 '16 at 22:27
  • @mm Add code, not prose please. – πάντα ῥεῖ Apr 17 '16 at 22:28
  • @πάντα ῥεῖ As the question is related to compiler transformations it is not relevant what is happening in other threads. The point is if the compiler is allowed to perform the presented transformation or not, in other words does the original code already contain a data race or is it introduced with the transformation. – mrn Apr 17 '16 at 22:43
  • @mm The compiler isn't aware about threading or concurrency regarding optimizations being applied. – πάντα ῥεῖ Apr 17 '16 at 22:47
  • 3
    @πάντα ῥεῖ It is aware. Please check the C11 standard, especially chapter *5.1.2.4 Multi-threaded executions and data races* and take a look at *NOTE 13*. It's where my question is coming from. – mrn Apr 17 '16 at 22:51
  • 2
    @Yakk: Actually, not that different. WG14 and WG21 worked together on concurrency. – MSalters Apr 18 '16 at 08:51
  • @Yakk-AdamNevraumont "_where C and C++ are utterly different languages_" Could you give examples of non-obvious differences? – curiousguy Jun 23 '18 at 23:55
  • @curiousguy No, because I don't know what is obvious to you. But these might be a start: https://stackoverflow.com/a/1201840/1774667 – Yakk - Adam Nevraumont Jun 24 '18 at 00:27

1 Answers1

5

In the C++ standard, a race is defined:

1.10/4: Two expression evaluations conflict if one of them modifies a memory location and the other one accesses or modifies the same memory location.

1.10/21: The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

Supposing that you have several threads running the same code, due to the fact that func() would always return 0 (your claim), none of the threads could change the content of x. Furthermore, y is a local variable of a function executed by a thread, so it is not shared. Hence, no race condition could occur in this scenario.

The compiler is not allowed to make the transformations corresponding to the second snippet because:

1.10/22: Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread in cases in which an abstract machine execution would not have encountered a data race.

But if yourself write the snippet, under the conditions explained above might encounter racing conditions since the x is not atomic, and there could be read access in one thread (temp=x) and write access in the other (either x=0 or in the default section of the other thread (x=temp)

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thanks for the answer, this is what I would expect, however I have doubts in one point. I don't understand how the fact that *func* always returns 0 influences the *happens-before* relation between the assignments to x in the switch statement and other accesses to x in different threads. – mrn Apr 17 '16 at 22:54
  • 1
    @mrn There is no happpens-before between threads other than that which synchronization provides. – Yakk - Adam Nevraumont Apr 18 '16 at 00:09
  • Happens-before relations are a tool to analysing sequencing propagation that arises from thread synchronisation during execution. If none of the thread uses the shared variable there is no other ordering effect beyond the fact that initial value happened beforr the rest. By the way, I dont see synchro in your code, and x is not atomic. so if funct would return something else than 0, races could occur because there is nothing that ensured an ordering of access to that variable. – Christophe Apr 18 '16 at 05:37
  • 1
    @Christophe Then according to what you're saying it looks like the data race is already in the original code. If x is accessed from two different threads and there is no happens-before relation between the access in the above switch statement and the one in a different thread, there is a data race according to 1.10/21. Or do you mean that if func() always returns zero there is no access to x at all in the switch statement, hence no data race, and the compiler must take such possibility into account? – mrn Apr 18 '16 at 06:28
  • Yes that's it: its the execution of a program that contain a race, not the programme itself. As long as function returns 0 you do not enter the problematic branches of the switch. If however in a particular execution the function might enter the branches a data race might occur. So when designing racing free code you have to think about all the possible execution path. Here i'd suggest to make x an atomic to eliminate the risk – Christophe Apr 18 '16 at 06:52
  • @Christophe Is the note 1.10/22 binding or is it a non-normative note? – mrn Apr 18 '16 at 16:45
  • 1
    This is a note, so non-normative. The note itself is not binding, but the wording "are generally precluded in this standard" is a hint that standard contains rules that in combination ensure the properties described in this note. – Christophe Apr 18 '16 at 17:58
  • 1
    @Christophe: If a compiler were allowed to, for arbitrary variables, insert "temp=theVariable; theVariable=0; theVariable=temp;" whenever it felt like it without regard for whether user code ever wrote the variable, any use of multi-threading would invoke UB since nothing would preclude a compiler's code for one thread trashing a variable just before the other thread was going to use it. – supercat Apr 18 '16 at 22:28
  • @supercat yes, that is what 1.10 is explaining with "preclude ... since ....data race" – Christophe Apr 18 '16 at 22:34