Is it necessary to have atomic flags in multi-threaded code?

守給你的承諾、 提交于 2020-03-23 11:56:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!