2
volatile global x = 0;
reader() {
  while (x == 0) {}
  print ("World\n");
}
writer() {
  print ("Hello, ")
  x = 1;
}
thread (reader);
thread (writer);

https://en.wikipedia.org/wiki/Race_condition#:~:text=Data%20race%5Bedit,only%20atomic%20operations.

From wikipedia,

The precise definition of data race is specific to the formal concurrency model being used, but typically it refers to a situation where a memory operation in one thread could potentially attempt to access a memory location at the same time that a memory operation in another thread is writing to that memory location, in a context where this is dangerous.

  1. There are at least one thread that writes to x. (writer)
  2. There are at least one thread that reads to x. (reader)
  3. There is not any synchronization mechanism for accessing x. (Both of two threads access x without any locks.)

Therefore, I think the code above is data race. (Obviously not a race condition) Am i right?

Then what is the meaning of data race when a code is data race, but it generates the expected output? (We will see "Hello, World\n", assuming processor guarantees that a store to an address becomes visible for all load instructions issued after the store instruction)

----------- added working cpp code ------------

#include <iostream>
#include <thread>

volatile int x = 0;

void reader() {
    while (x == 0 ) {}
    std::cout << "World" << std::endl;
}

void writer() {
    std::cout << "Hello, ";
    x = 1;
}

int main() {
    std::thread t1(reader);
    std::thread t2(writer);
    t2.join();
    t1.join();
    return 0;
}
Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
Jangwoong Kim
  • 121
  • 10
  • I meant the thread that executes *writer* will write 1 to *x*, which will stop *reader* from spinning. I also meant the function *thread* spwans a thread (and not join it), just like *std::thread* in cpp. – Jangwoong Kim Apr 16 '22 at 16:28
  • Since the shared variable `x` is `volatile`, assuming Java-like memory model, there is no data race because every read-write from-to `x` is atomic. Without `volatile`, there is a race, and there is no guarantee that the `reader` thread will ever see the write to `x` – Burak Serdar Apr 16 '22 at 16:38
  • @matt, There was error in code. *reader* should have spinned until *x* is zero, not *x* is NOT zero. – Jangwoong Kim Apr 16 '22 at 16:45
  • @BurakSerdar, I assummed C++ memory model, where *volatile* does not indicate atomic. In C++, *volatile* just negates any changes for memory optimization by compiler. If *volatile* indicates atomic in Java memory model, i totally agree with you. But i want the answer in the context of C++ memory model. – Jangwoong Kim Apr 16 '22 at 16:51
  • Becuase in C++, *reader* will see the change to *x* and the code will get the right result while having data race. – Jangwoong Kim Apr 16 '22 at 16:58
  • Why do you say it generates the "expected output"? Why do you expect the race to come out one particular way given that the standard says such races are undefined behavior? – David Schwartz Apr 16 '22 at 20:04
  • @DavidSchwartz, Now I see the code might not make the expected output. Though, as the answerer said, on many modern platform access to *int* is atomic , which leads to right output. – Jangwoong Kim Apr 17 '22 at 05:27

2 Answers2

4

Yes, data race and consequently undefined behavior in C++. Undefined behavior means that you have no guarantee how the program will behave. Seeing the "expected" output is one possible output, but you are not guaranteed that it will happen.

Here x is non-atomic and is read by thread t1 and written by thread t2 without any synchronization and therefore they cause a data race.

volatile has no impact on whether or not an access is a data race. Only using an atomic (e.g. std::atomic<int>) can remove the data race.

That said, on many common platforms writing to a int will be atomic on the hardware level, the compiler will not optimize away volatile accesses and will probably also not reorder volatile accesses with IO and therefore it will probably happen to work on these platforms. The language doesn't make this guarantee though.

user17732522
  • 53,019
  • 2
  • 56
  • 105
4

Yes, this is a data race and UB.

[intro.races]/2

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

[intro.races]/21

Two actions are potentially concurrent if:
— they are performed by different threads, ...

...

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, ...

Any such data race results in undefined behavior.

For two things in different threads to "happen before" one another, a synchronization mechanism must be involved, such as non-relaxed atomics, mutexes, and so on.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207