0

I recently asked "Thrown object cannot be caught in a multi-threaded solution" and got the correct answer which works perfectly. However, I am still confused why there can be a race condition when only one thread does the write operation. Let me paste the original problematic code:

#include <iostream>
#include <thread>
using namespace std;

struct solution_using_thread {
    solution_using_thread()
     : alive_(true), thread_() {
        thread_ = thread([this]() {
            while(alive_);
        });
    }
    ~solution_using_thread() {
        alive_ = false;
        thread_.join();
    }
private:
    bool alive_;
    thread thread_;
};

int main() {
    cout << 0 << endl;
    try {
        solution_using_thread solution;
        throw 1;
    } catch (int i ) {
        cout << i << endl;
    }
    cout << 2 << endl;
}

Sometimes the output is only

0

According to the linked question, if I instead use member atomic<bool> alive_, the output becomes as expected

0
1
2

Now, I am trying to reason why member bool alive_ causes Undefined Behavior.

Case 1 (Happy ending):

  • Variable solution is initialized:
    • solution_using_thread's default constructor sets alive_ to true in the main thread.
    • The thread starts and the value of alive_ happens to be true in the second thread. So thread execution is stuck in the while loop.
    • Before constructor returns, the second thread has already been started.
  • We throw 1.
    • The destructor of solution is called. The value of alive_ is true in the main thread.
    • thread.join() blocks until the value of alive_ is synchronized with the second thread.
    • After some finite amount of delay alive_ is synchronized, the while loop terminates, the second thread finishes, the thread_.join() returns and stack unwinding is happily completed.
  • The output is 0 1 2

Case 2 (Not desired, but at least not 'Undefined Behavior'):

  • Variable solution is initialized:
    • solution_using_thread's default constructor sets alive_ to true in the main thread.
    • The thread starts and the value of alive_ happens to be false in the second thread. So thread execution ends immediately.
    • Before constructor returns, the second thread have already been started.
  • We throw 1
    • thread.join() returns immediately because the thread has already finished.
  • The output is 0 1 2

Obviously there is at least one more case in which it prints only 0. Can you describe that case?

Community
  • 1
  • 1
Benji Mizrahi
  • 2,154
  • 2
  • 23
  • 38
  • 1
    General rule: Don't try to understand undefined behaviour. And absolutely don't try to second-guess and "work around" it. [Recommended video](https://www.youtube.com/watch?v=uHCLkb1vKaY) – Kerrek SB Mar 03 '15 at 08:28

1 Answers1

1

The Standard says that if multiple threads access a variable and at least one access is a write, then if the variable isn't atomic and the operations aren't ordered, there is a data race, and a program with a data race has undefined behaviour.

Knowing these rules, the compiler can assume that a non-atomic variable is not modified out of order (since otherwise any program is a valid interpretation of your source code); in your example code, this means that the compiler can simply assume that alive_ never changes inside the busy loop -- though by the way, a non-terminating loop like that is itself undefined behaviour.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084