Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multithreading program stuck in optimized mode but runs normally in -O0

I wrote a simple multithreading programs as follows:

static bool finished = false;  int func() {     size_t i = 0;     while (!finished)         ++i;     return i; }  int main() {     auto result=std::async(std::launch::async, func);     std::this_thread::sleep_for(std::chrono::seconds(1));     finished=true;     std::cout<<"result ="<<result.get();     std::cout<<"\nmain thread id="<<std::this_thread::get_id()<<std::endl; } 

It behaves normally in debug mode in Visual studio or -O0 in gcc and print out the result after 1 seconds. But it stuck and does not print anything in Release mode or -O1 -O2 -O3.

like image 248
sz ppeter Avatar asked Oct 23 '19 05:10

sz ppeter


People also ask

What is true about run method in multi threading *?

The run() method of thread class is called if the thread was constructed using a separate Runnable object otherwise this method does nothing and returns. When the run() method calls, the code specified in the run() method is executed. You can call the run() method multiple times.

Why it is difficult to debug multi threaded programs?

Parallel processing using many threads can greatly improve program performance, but it may also make debugging more difficult because you're tracking many threads. Multithreading can introduce new types of potential bugs.

What happens when two threads of the same program run concurrently?

In the same multithreaded process in a shared-memory multiprocessor environment, each thread in the process can run concurrently on a separate processor, resulting in parallel execution, which is true simultaneous execution.


1 Answers

Two threads, accessing a non-atomic, non-guarded variable are U.B. This concerns finished. You could make finished of type std::atomic<bool> to fix this.

My fix:

#include <iostream> #include <future> #include <atomic>  static std::atomic<bool> finished = false;  int func() {     size_t i = 0;     while (!finished)         ++i;     return i; }  int main() {     auto result=std::async(std::launch::async, func);     std::this_thread::sleep_for(std::chrono::seconds(1));     finished=true;     std::cout<<"result ="<<result.get();     std::cout<<"\nmain thread id="<<std::this_thread::get_id()<<std::endl; } 

Output:

result =1023045342 main thread id=140147660588864 

Live Demo on coliru


Somebody may think 'It's a bool – probably one bit. How can this be non-atomic?' (I did when I started with multi-threading myself.)

But note that lack-of-tearing is not the only thing that std::atomic gives you. It also makes concurrent read+write access from multiple threads well-defined, stopping the compiler from assuming that re-reading the variable will always see the same value.

Making a bool unguarded, non-atomic can cause additional issues:

  • The compiler might decide to optimize variable into a register or even CSE multiple accesses into one and hoist a load out of a loop.
  • The variable might be cached for a CPU core. (In real life, CPUs have coherent caches. This is not a real problem, but the C++ standard is loose enough to cover hypothetical C++ implementations on non-coherent shared memory where atomic<bool> with memory_order_relaxed store/load would work, but where volatile wouldn't. Using volatile for this would be UB, even though it works in practice on real C++ implementations.)

To prevent this to happen, the compiler must be told explicitly not to do.


I'm a little bit surprised about the evolving discussion concerning the potential relation of volatile to this issue. Thus, I'd like to spent my two cents:

  • Is volatile useful with threads
  • Who's afraid of a big bad optimizing compiler?.
like image 166
Scheff's Cat Avatar answered Sep 24 '22 10:09

Scheff's Cat