0

There are three question about the code snippet below.

  1. When the macro(i.e. NO_STUCK_WITH_OPTIMIZATION ) is not enabled, why this code snippet gets stuck when the optimization is enabled(i.e. -O1, -O2 or -O3) whereas the program works well if the optimization is not enabled?
  2. And why the program no longer get stuck if std::this_thread::sleep_for() is added?

UPDATED: 3. If the is_run is declared as volatile(for details, see the code snippet), then the program would never get stuck on X86? ^^UPDATED ENDS^^

    #include <functional>
    #include <thread>
    #include <iostream>
    #include <chrono>
    #include <atomic>
    
    #ifdef NO_STUCK_WITH_OPTIMIZATION 
    using TYPE = std::atomic<int>;
    #else
    using TYPE = int;  //The progrom gets stuck if the optimization is enabled.
    #endif
    
    int main()
    {
        TYPE is_run{1};
        auto thread = std::thread([&is_run](){while(1==is_run){
              //std::this_thread::sleep_for(std::chrono::milliseconds(10)); //If this line is added, the program is no longer gets stuck. Why?
                                            }
            std::cout << "thread game over" << std::endl;
        });
    
        std::this_thread::sleep_for(std::chrono::seconds(1));
        is_run = 0;
        thread.join();
    }
John
  • 2,963
  • 11
  • 33
  • 2
    Just to make sure your question is understood, are you asking "how come when I use non thread-safe objects with multiple execution threads my code ends up being broken"? – Sam Varshavchik Apr 03 '22 at 13:05
  • See https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races – Passer By Apr 03 '22 at 13:05
  • Sorry for my poor English. My title hopes to indicate two questions. First, why the code snippet gets stuck when the optimization is enabled whereas the program works well if the optimization is not enabled. Second, why the code snippet does not get stuck any more if `std::this_thread::sleep_for` is added. – John Apr 03 '22 at 13:07
  • Because this is what "undefined behavior" means. Without proper thread safety, all guarantees are void. No refunds are given. Caveat emptor. You're on your own. The program may or may not work, and whether it does or does not work depends on compilation options, the time of day, the phase of the moon, current weather, or any other factor. It matters very little the reason for the specific manifestation of undefined behavior. It's undefined behavior. – Sam Varshavchik Apr 03 '22 at 13:10
  • *Why the code snippet gets stuck when optimization is enabled?* Because the program has a bug in it. It is using a variable in two threads, without interthread coordination such as an atomic or a mutex. – Eljay Apr 03 '22 at 13:30

1 Answers1

3

You have a multi-threaded program. One thread does is_run = 0;.

The other thread does while(1==is_run). Although you guarantee, with a sleep (matter for another question), that the write is done before the read, you need to tell the compiler to synchronize this variable.

In C++, the simple way to make sure one thread sees the change is to use atomic<int>. If you don't do this, the other thread might never see the change. This hiding of the change might be due to local optimization of the code at compile time, by the OS deciding not to refresh some memory page or by the hardware itself deciding the memory doesn't need to be reloaded.

Putting the atomic variable guarantees all those systems know what you want to do. So, use it. :-)

Lifting from the comments:

https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races

A program that has two conflicting evaluations has a data race unless both evaluations execute on the same thread or in the same signal handler, or both conflicting evaluations are atomic operations (see std::atomic), or one of the conflicting evaluations happens-before another (see std::memory_order) If a data race occurs, the behavior of the program is undefined.

Jeffrey
  • 11,063
  • 1
  • 21
  • 42
  • If the `is_run` is declared as `volatile`(for details, see [the code snippet](https://godbolt.org/z/GscshYhjr)), then the program would never get stuck [**on X86**](https://codywu2010.wordpress.com/2014/11/03/is-int-write-operation-atomic-on-x86_64/)? – John Apr 03 '22 at 14:59
  • 1
    @John Yes, with `volatile` the compiler is not allowed to optimize away the read so the program will work on x86. But I think the program is still technically *undefined behavior*., it might fail on platforms with looser memory models. Don't use `volatile` for multithreading purpose, only `std::atomic` or so. – prapin Apr 03 '22 at 15:29
  • yeah, don't use `volatile`. This keyword was added before C++ had a memory model. It will make some issues go away (by preventing some compiler optimization), but not all and your code would still have UB. – Jeffrey Apr 03 '22 at 16:14
  • @Jeffrey I create a new question, let us discuss at [here](https://stackoverflow.com/q/71731573/13611002). – John Apr 04 '22 at 03:00