我想知道在多线程代码中是否真的需要原子标记。对于这个问题,我着眼于多线程代码中的一种常见情况:通过设置标志来停止线程。
假设以下伪代码:
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; | } | } | }
在此伪代码中,所有running_thread_x
线程都使用公共变量is_running
来检查它们是否正在运行。当我们想在stopper_thread
中停止它们时,只需将is_running
设置为0
。这意味着is_running
是线程之间的共享资源。在许多编码示例中,人们都使用std::atomic_flag
标志使用原子变量(例如C ++中的is_running
)或在关键部分访问此变量,以互斥访问此变量。
但是是否需要同步此标志?
我以某种方式相信,在与上述示例类似的情况下,其中只有stopping operation作为一个或多个stopper线程,实际上没有必要同步对该标志的访问。
为什么?
据我所知,即使我们要停止线程时也可以同时访问多个线程中的is_running
标志,此访问不会阻止通过以下方式将该标志从1
设置为0
:止动螺纹。发生的情况是此更改可能不会立即反映在正在运行的线程中。但这重要吗?我认为不是,因为如果我们不在当前正在运行的线程迭代中从0
读取值is_running
,那么您将在再经过几次迭代后最终读取它,并且线程将最终停止。因此设置此标志最终将停止所有正在运行的线程,但停止可能会延迟一点。
您如何看待我的论点?我的论点正确吗?还是我可能缺少论证失败的情况?
[std::mutex
/ pthread_mutex_t
和std::condition_variable
/ pthread_cond_t
用于与线程通信时,该标志不应是原子的,因为仅当互斥锁被锁定时,该标志才必须存储和加载。尝试将std::atomic
/atomic_flag
/ atomic_bool
用于标志来绕过互斥锁会导致死锁。
例如:
+-----+--------------------------------+--------------------------------+
|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. |
+-----+--------------------------------+--------------------------------+
在这种情况下,线程A可以在B完成步骤2之后但在完成步骤5之前执行步骤3和4。在这种情况下,丢失了来自步骤4的条件变量通知,导致线程B在步骤5中等待条件变量永远。
发生的情况是此更改可能不会立即反映在正在运行的线程中。
发生的事情是这是未定义的行为。允许编译器使用非同步代码执行几乎所有操作。例如,可以重写
while(is_running) { }
进入
auto running = is_running;
while(running) { }
因此,它将永远循环,无论is_running
的未来值如何。当is_running
声明为atomic时,不允许进行此重写。
此外,即使编译器不重写此代码,也没有原子性,仍然允许CPU执行它(它可以从缓存而不是内存中读取陈旧的值。
人们使用原子的原因是为了避免UB。如果执行多线程,则在同步线程时必须使用同步原语。没有逃生。