问题
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?
回答1:
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.
回答2:
When std::mutex
/pthread_mutex_t
and std::condition_variable
/pthread_cond_t
are used to communicate with the thread the flag shouldn't be atomic because it must be stored to and loaded from only when the mutex is locked. Trying to use std::atomic
/atomic_flag
/atomic_bool
for the flag to bypass mutex locking leads to a deadlock.
E.g.:
+-----+--------------------------------+--------------------------------+
|Step |Thread A |Thread B |
+-----+--------------------------------+--------------------------------+
|1 | |lock the mutex |
+-----+--------------------------------+--------------------------------+
|2 | |check whether the flag is not |
| | |set or the queue is empty |
+-----+--------------------------------+--------------------------------+
|3 |set the atomic flag | |
+-----+--------------------------------+--------------------------------+
|4 |notify condition variable |<notification is lost> |
+-----+--------------------------------+--------------------------------+
|5 | |and if so wait on the condition |
| | |variable. |
+-----+--------------------------------+--------------------------------+
In this scenario thread A can do steps 3 and 4 after B did step 2 but before it has done step 5. In this case the condition variable notification from step 4 is lost leading thread B to wait on the condition variable in step 5 forever.
来源:https://stackoverflow.com/questions/60071773/is-it-necessary-to-have-atomic-flags-in-multi-threaded-code