我在哪里设置内存屏障以便条件循环观察多线程值的变化?
Posted
技术标签:
【中文标题】我在哪里设置内存屏障以便条件循环观察多线程值的变化?【英文标题】:Where do I set memory barriers so that conditional loops observe multithread value changes? 【发布时间】:2021-09-02 13:24:46 【问题描述】:假设一个线程无限期地运行,直到它的成员变量 stop 被设置。喜欢
while (!stop)
// do something
我想防止它变成这样
if (!stop)
while (true)
那么我应该在哪里设置内存屏障/栅栏以使这种优化无法执行?
此外,这样的栅栏是否足以确保在下一次迭代中可以看到从另一个线程对true
的更改?
【问题讨论】:
stop
应该是 std::atomic
。这样,编译器将插入必要的障碍。否则,程序会通过数据竞争表现出未定义的行为。
@IgorTandetnik,是的,但从技术上讲,原子会在变量访问之前或之后放置内存屏障。我认为内存屏障意味着编译器无法在之前和/或之后重新排序访问。但是在stop
上只有那篇文章。因此,从技术上讲,它甚至没有针对任何其他内存访问进行重新排序
@IgorTandetnik 可以在没有原子的情况下使用 std::atomic_thread_fence
或条件变量来完成,但是是的,原子应该是最简单且不易出错的方式。
@Regyn "原子是否会在变量访问之前或之后放置内存屏障" 它将按和内存访问的顺序排列。访问本身就像一个内存栅栏。
@DrewDormann 所以内存栅栏在例如atomic 会强制编译器不对读取进行重新排序,因此只执行一次?
【参考方案1】:
那么我应该在哪里设置内存屏障/栅栏以使这种优化无法执行?
memory barrier/fence 限制了memory ordering,从而防止编译器重新排序同一线程中的两个或多个内存访问。但是,这里的内存排序不是问题,因为您只是在谈论每个线程的单个内存访问。
相反,您说的是来自两个不同线程对同一变量的两个内存操作。因此,这是thread synchronization 的问题,而不是内存排序问题。
根据§6.9.2.2 ¶21 of the ISO C++20 standard,如果两个线程在没有同步操作(例如互斥锁)的情况下访问同一个变量,并且如果这些内存访问不是
-
都是原子的,或者
都是只读的,
那么这会导致未定义的行为。
因此,对于您的 while
循环
while (!stop)
// do something
假设stop
是一个非原子数据类型,编译器可以假设变量stop
在循环运行时永远不会被另一个线程修改(除非你碰巧在里面有同步操作循环),因为这样做会导致未定义的行为(这意味着编译器将被允许做任何事情)。
因此,一个好的优化编译器会有效地将代码更改为以下内容:
if (!stop)
while (true)
// do something
但是,如果将变量stop
更改为原子数据类型,例如std::atomic_flag
或std::atomic<bool>
,那么多个线程同时访问该变量并不是未定义的行为。在这种情况下,编译器将不允许假设stop
不会被另一个线程修改,从而不允许执行上述优化。
【讨论】:
我想知道的是:atomic bool 的构造是什么来表明发生了可能会修改变量并因此阻止优化的事情。编译器是否检查原子并识别它有这样的副作用? @Regyn:当变量是原子的时,编译器会意识到这一点,并将对该变量的所有读写操作进行原子操作。编译器知道原子变量可以随时被不同的线程修改。因此,编译器知道不允许优化对原子变量的读取或写入。无需明确告诉编译器原子变量可能被不同的线程修改。通过将变量声明为原子变量(即使用std::atomic_flag
或std::atomic
)已经暗示了这一点。【参考方案2】:
很短,在cmets中已经讲过了,但答案是:
std::atomic<bool> stop;
让编译器处理栅栏。
【讨论】:
是的。内存栅栏对您的应用程序来说太多或太少,因此它会不必要地损害性能而不保证您需要的行为。它太少了,因为它没有可见性保证。这太过分了,因为它对所有其他内存访问施加了排序限制。【参考方案3】:正如已经回答的那样,一个简单的:
std::atomic<bool> stop;
足够了,就像operations on atomic variables are ordered in a sequentially-consistent manner。
但是,释放语义可以用于存储并在加载时获取语义以达到类似的效果:
bool side_effects = false;
std::atomic<bool> stop = false;
std::thread do_stop(
[&]
side_effects = true;
stop.store(true, std::memory_order_release);
);
while(!stop.load(std::memory_order_acquire))
// Any side effect ordered before the release store is necessarily
// visible after such store is sensed by the acquire load.
assert((side_effects));
do_stop.join();
看live。
【讨论】:
以上是关于我在哪里设置内存屏障以便条件循环观察多线程值的变化?的主要内容,如果未能解决你的问题,请参考以下文章