Do I need to use std :: atomic to signal the completion of a thread?

I would like to check if std::thread completed execution. Search for stackoverflow I found the following question that solves this problem. The accepted answer suggests that the worker thread set the variable just before exiting and the main thread check this variable. Here is a minimal working example of such a solution:

 #include <unistd.h> #include <thread> void work( bool* signal_finished ) { sleep( 5 ); *signal_finished = true; } int main() { bool thread_finished = false; std::thread worker(work, &thread_finished); while ( !thread_finished ) { // do some own work until the thread has finished ... } worker.join(); } 

Someone who commented on the accepted answer claims that you cannot use the simple bool variable as a signal, the code was broken without a memory barrier, and using std::atomic<bool> would be correct. My initial hunch is that this is wrong and a simple bool enough, but I want to make sure that I'm not missing something. Is std::atomic<bool> required for the above code correctly?

Suppose that the main thread and the worker are running on different CPUs in different sockets. I think it will happen that the main thread reads thread_finished from its processor cache. When an employee updates it, the cache coherence protocol commits itself to write the working changes to the global memory and invalidate the processor cache of the main thread so that it reads the updated value from the global memory. Doesn't everything indicate that cache coherency makes code like the one above just work?

+16
c ++ c ++ 11 stdatomic stdthread
Jan 16 '13 at 18:52
source share
4 answers

Someone who commented on the accepted answer claims that you cannot use a simple bool variable as a signal, the code was broken without a memory barrier, and using std :: atomic would be correct.

The comment is correct: a simple bool not enough, because non-atomic entries from a thread that sets from thread_finished to true can be reordered.

Consider a thread that sets the static variable x to some very important number, and then signals its output, for example:

 x = 42; thread_finished = true; 

When your main thread sees thread_finished set to true , it assumes the worker thread has completed. However, when your main thread checks for x , it can determine that it is set to the wrong number because the two entries above were reordered.

Of course, this is just a simplified example illustrating a common problem. Using std::atomic for your thread_finished variable adds a memory barrier, ensuring that all writes are completed before it is complete. This fixes a potential out-of-band recording problem.

Another problem is that reading non-volatile variables can be optimized therefore, the main thread will never notice a change in the thread_finished icon.




Important notice: make your thread_finished volatile not to fix the problem; in fact, volatile should not be used in conjunction with stream processing - it is designed to work with hardware mapped to memory.
+21
Jan 16 '13 at 19:04 on
source share

Using raw bool not enough.

A program execution contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and does not occur before the other. Any such data race results in undefined behavior. Β§ 1.10 p21

Two evaluations of expressions contradict each other if one of them changes the memory location (1.7), and the other receives or changes the same memory cell. Β§ 1.10 p4

Your program contains a data race in which the worker thread writes to bool, and the main thread reads from it, but there are no formal events - up to the relationship between operations.

There are many ways to avoid data race, including using std::atomic<bool> with appropriate memory orders, using a memory barrier, or replacing bool with a condition variable.

+6
Jan 16 '13 at
source share

It is not normal. The optimizer can optimize

  while ( !thread_finished ) { // do some own work until the thread has finished ... } 

at

  if(!thread_finished) while (1) { // do some own work until the thread has finished ... } 

assuming he can prove that "some own work" does not change thread_finished .

+2
Jan 16 '13 at 19:03
source share

Cache coherence algorithms are not everywhere and not perfect. The problem with thread_finished is that one thread is trying to write a value for it, and another thread is trying to read it. This is a data race, and if the calls are not ordered, this leads to undefined behavior.

+2
Jan 16 '13 at 19:04 on
source share



All Articles