1

Based on the info of the output, can anyone explains the code below?

How can all of (a==1 && a==2 && a==3) be true?

#include <iostream>
#include <thread>

int a = 0;

int main()
{
  std::thread runThread([]() { while (true) { a = 1; a = 2; a = 3; }});
  while (true)
  {
    if (a == 1 && a == 2 && a == 3)
    {
      std::cout << "Hell World!" << std::endl;
    }
  }
}

Output:

Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!
Hell World!

...


January 2019

I think this question is highly relevant to this link -> C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?

Kiseong Yoo
  • 97
  • 1
  • 7
  • 1
    What do you mean by "possible"? The program does what it does. What exactly did you expect? – freakish Oct 02 '18 at 17:22
  • 1
    It is usually "Hello World!" :-) – Tryer Oct 02 '18 at 17:22
  • 1
    @Tryer You mean "Hell World!"? – François Andrieux Oct 02 '18 at 17:23
  • This code contains undefined behavior, including unsynchronized read and write to `int`. – François Andrieux Oct 02 '18 at 17:24
  • 2
    @pm100 This has undefined behavior, so it might be `true` at any time. And it seems like it might actually happen in OP's case. – François Andrieux Oct 02 '18 at 17:26
  • If this is an example your professor or teacher is giving you to teach you about threads, then they are a very poor instructor. – François Andrieux Oct 02 '18 at 17:27
  • Also, an infinite cycle that makes no I/O, no synchronization, no volatile access and no atomic access has undefined behavior in C++. The cycle in the thread function is undefined for that reason. This can be fixed by making `a` atomic. The observed effect should persist. – AnT Oct 02 '18 at 17:29
  • to echo what @FrançoisAndrieux said about images. The `=` in the lamda _really_ looks like a `-` on my crappy monitor. Please avoid images when possible. – Brad Allred Oct 02 '18 at 17:29
  • If you compile with optimization it will be empty loop. – Anty Oct 02 '18 at 17:31
  • Why do you mention "critical section" in the title of your question and in the tags? What's the relevance of critical sections to your question? – AnT Oct 02 '18 at 17:32
  • I suggest you add an `else` branch to print an alternate message. perhaps that will clarify what is happening; in other words sometimes you get "lucky". – Brad Allred Oct 02 '18 at 17:34
  • I’m asking this on mobile I attached a photo, sorry. OK, I’ll edit my question asap. – Kiseong Yoo Oct 02 '18 at 17:34
  • Regarding "Hell World": [Finnish Operatic Metal.](https://www.youtube.com/watch?v=_q6chUtSef4) – user4581301 Oct 02 '18 at 17:38
  • 1
    @FrançoisAndrieux if the professor is trying to give students experience regarding race conditions and undefined behavior, then this is a pretty good example of that. – Jeremy Friesner Oct 02 '18 at 17:41

2 Answers2

10

You are experiencing undefined behavior.

Your code has a race condition; one thread is reading a while another is writing, and no synchronization occurs. You aren't allowed to do this in C++. Programs that do that can be compiled to do anything at all.

In fact, on a release build, I'd expect that to compile down to if(false). The compiler optimizes the main thread, notices no synchronization, proves that a cannot be 3 different values without UB, and optimizes out the if.

On a debug build, I could expect the symptoms you see. Not because it is more correct, but because debug builds tend not to trip over that kind of undefined behavior.

So the first thing you have to do to talk about your program reasonably is remove the undefined behavior: make a a std::atomic<int> instead of an int.

Now in both release and debug you'd expect to see ... exactly what your test showed. Or nothing. Or anything in between. The result is no longer undefined, but it remains non-deterministic.

The if statement isn't atomic. Between the conditions, a can change. And with a program running forever it should happen sometimes, as the other thread is changing it.

The resulting program is well defined. Even forward progress guarantees are ok, because you read an atomic variable.

Yakk - Adam Nevraumont
  • 250,370
  • 26
  • 305
  • 497
4

You probably compiled your program with optimization disabled, so the assembly / machine-code for the target architecture you're compiling for actually does all the steps in the C++ abstract machine in the order they appear, including actually storing or loading to/from RAM. (Debug builds typically treat every variable as volatile.)

On typical architectures where a 32-bit int load/store is naturally atomic, a debug build behaves much like a portable C++ program with no undefined behaviour using std::atomic<int> a with a.store(1, std::memory_order_relaxed) (or on x86, std::memory_order_release).


The program starts a thread which repeatedly set the value a to 1, then 2, then 3. (This is the runThread line).

The main thread then tests whether a is equal to 1 and a is equal to 2 and a is equal to 3. And prints "hello world" if it is equal to all three. (this happens in the second while(true))

The reason why it does print "hello world" is because of concurrency. It happens that while one thread performs the three tests, the other thread writes the right values just at the right times. Remember that the three parts of the if are done one after each other


This is nowhere guaranteed to happen. It could be optimized out if you weren't making a debug build. But given the speed computers run at, and a typical implementation of threads, it does happen.

Do not rely on this behaviour. Rather, try to understand that the two threads perform operations concurrently.

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731
Jeffrey
  • 9,703
  • 1
  • 17
  • 36
  • 2
    This answer explains one *possible* result of undefined behavior, but the program could do anything. I would actually expect the `if` condition to be optimized out for always being `false`, so it would never print. Though that's also just another possibility. – François Andrieux Oct 02 '18 at 17:30
  • 1
    Yeah, and I realized this forum is very strong about telling "this is UB, don't do it" strongly and consistently. I hoped it would be useful to the OP to learn _how_ it could possibly happen, though. Recognizing UB is one skill. Seeing possible thread flow is another. – Jeffrey Oct 02 '18 at 17:42
  • 1
    @FrançoisAndrieux: I updated this answer to explain why the UB turned into machine code that works this way, for a debug build on a "normal" CPU architecture with a normal compiler. Now it's correct, IMO, and gives the right context for its fairly good explanation of the nuts and bolts of the effect, that `a` can change between each step of the `&&` condition. – Peter Cordes Oct 03 '18 at 08:23