考虑以下代码:
#include <atomic>
std::atomic<bool> flag;
void thread1()
{
flag.store(true, std::memory_order_relaxed);
}
void thread2()
{
while (!flag.load(std::memory_order_relaxed)
;
}
在标准下,编译器可以优化
thread1
中的存储(因为 thread1
没有发布围栏)?使 thread2
成为无限循环?或者,编译器是否可以在从内存中读取一次后在 flag
中注册缓冲区 thread2
(无论 thread2
是如何编写的,使得 flag
可能成为一个无限循环)?
如果这是一个潜在的问题,
volatile
会解决它吗?
volatile std::atomic<bool> flag;
引用标准的答案将不胜感激。
不,将
std::atomic
load
提升到循环之外(并发明一个无限循环)会违反标准的指导,即存储“should”对加载可见“in a reasonable amount of time” [ atomics.order]和“有限的时间段”[intro.progress].
为什么要使用 `memory_order_seq_cst` 设置停止标志,如果您使用 `memory_order_relaxed` 检查它? 询问一个关于等效
exit_now
自旋循环的稍微不同的事情(更担心保持线程间延迟低,而不是而不是担心它是无限的),但适用于标准中的相同引用。
CPU 尽快将存储提交给 缓存一致性内存;更强的命令(和
fence
es)只是让this线程等待事情,例如在获取其他负载的值之前完成获取负载。或者在发布存储提交到 L1d 缓存并且其本身变得全局可见之前完成早期存储和加载。栅栏不会将数据推送到其他线程,它们只是控制发生这种情况的顺序。数据变得全球可见的速度非常快。 (如果你在假想的硬件上实现 C++ 而不是以这种方式工作,你甚至必须编译一个轻松的存储以包含额外的指令来刷新它自己的地址。)
store
绝对不能因为那个和其他原因而被优化掉:它是一个改变程序状态的可见副作用。它保证对该线程中的后续加载可见(例如,在 thread2
函数的调用者中)。出于同样的原因,即使是非原子 plain_bool = true
赋值也无法优化掉,除非内联到调用者之后执行 plain_bool = false
;然后可能会发生死店消除。
编译器目前不优化原子,基本上像
volatile atomic<>
一样对待它们,但是ISO C++would允许优化原子flag=true;
flag=false;
到flag=false;
(即使有seq_cst
,但也与.store(val, relaxed)
)。这可以消除其他线程检测到变量是true
的时间窗口; ISO C++ 不保证抽象机中存在的任何状态实际上可以被另一个线程观察到。
但是,作为实施质量问题,优化解锁/重新锁定或
++
/ --
可能是不可取的,这是编译器不优化原子的部分原因。也相关:如果 RMW 操作没有任何改变,它可以优化掉,对于所有内存顺序吗? - 将两个 RWM 合并到一个空操作中不能优化掉它们的内存排序语义,除非它们都是relaxed
任何地方都没有围栏,包括可能的来电者。
即使编译器确实按照假设规则进行了标准允许的尽可能多的优化,对于这种情况您仍然不需要
volatile atomic
(假设thread2()
的调用者没有在之后立即执行flag.store(false, order)
电话)。
但是在其他情况下您可能想要
volatile atomic
。但是 http://wg21.link/p0062 / http://wg21.link/n4455 指出即使是 volatile atomic
也没有关闭所有可能的过度优化的漏洞,所以直到进一步的设计进展是为了让程序员控制何时可以优化原子,计划是编译器将继续像现在一样运行,而不是优化原子。