I was wondering if it is really necessary to have atomic flags in a multi-threaded code. For this problem, I focus on a common situation in multi-thread code: stopping threads by setting a flag.
Let's assume following pseudo-code:
is_running = 1;
create_threads(stopper_thread, running_thread_A, running_thread_B, running_thread_C);
stopper_thread running_thread_A running_thread_B running_thread_C
-------------------------------------------------------------------------------------------
if (x) | while(is_running) { | while(is_running) { | while(is_running) {
is_running = 0; | } | } | }
In this pseudo-code, all running_thread_x threads use common variable is_running to check if they are running or not. When we want to stop them in stopper_thread, we just set is_running to 0. this means that is_running is a shared resource between threads. In a lot of coding samples, people use atomic variables (for example std::atomic_flag in C++) for is_running flag or access to this variable in a critical section to provide mutual exclusion in accessing this variable.
But is synchronizing this flag necessary?
I somehow believe that in situations similar to aforementioned example where there is just stopping operation as single or multi stopper thread(s), it is practically not necessary to synchronize access to this flag.
Why?
Because as far as I know, even if we have simultaneous access to is_running flag in multiple threads when we want to stop threads, this access does not prevent from setting this flag from 1 to 0 by stopper thread. What happens is that this change may not be reflected in running threads immediately. But is this important? I think not, because if we don't read value 0 from is_running in current iteration of running threads, you will finally read it after few more iterations and thread will be stopped finally. So setting this flag will finally stops all running threads, but stopping may be delayed a little.
What do you think about my argument? Is my argument correct? or I may be missing a situation that my argument fails?
What happens is that this change may not be reflected in running threads immediately.
What happens is that this is an undefined behaviour. The compiler is allowed to do pretty much anything with non-synchronized code. For example it is allowed to rewrite
while(is_running) { }
into
auto running = is_running;
while(running) { }
when condition doesn't change inside while body.
And so it will loop forever, regardless of future values of is_running. This rewrite is not allowed when is_running is declared as atomic.
Moreover without atomic even if the compiler doesn't rewrite this code, the CPU is still allowed to do it (it may read the stale value from cache, not memory).
The reason people use atomics is to avoid UB. If you do multi threading then you have to use synchronization primitives whenever you synchronize threads. There's no escape.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With