std::atomic:当任务循环时内存屏障是否有效?
std::atomic: Does the memory barrier hold up when task loops around?
所以说我有这个构造,其中两个任务 运行 同时(伪代码):
int a, b, c;
std::atomic<bool> flag;
TaskA()
{
while (1) {
a = 5;
b = 2;
c = 3;
flag.store(true, std::memory_order_release);
}
}
TaskB()
{
while (1) {
flag.load(std::memory_order_acquire);
b = a;
c = 2*b;
}
}
内存屏障应该在标志变量处。据我了解,这意味着任务 B 中的操作(b = a 和 c = 2b)在任务 A 中的赋值(a = 5、b = 2、c = 3)之后执行。但这是否也意味着,当 TaskB 仍然处于 c = 2*b 时,TaskA 不可能已经循环并再次执行 b = 2 ?这是否以某种方式被阻止了,我是否需要在循环开始时设置第二个障碍?
如果您在发布存储后立即开始另一次非原子变量写入,再多的障碍也无法帮助您避免数据争用 UB。
当您的 reader 正在读取这些变量,因此在 C 抽象机中你有数据争用 UB。 (在您的示例中,来自 a
的非同步写入+读取、b
的非同步写入+写入以及 b
的写入+读取以及 c
的写入+写入.)
此外,即使 没有 循环,您的示例仍然无法安全地避免数据争用 UB,因为您的 TaskB 访问 a
、b
和c
无条件后flag.load
。因此,无论您是否观察到作者发出的 data_ready = 1 信号,表示变量已准备好被读取,您都会执行这些操作。
当然,在实际实现的实践中,重复写入 相同的 数据不太可能在这里引起问题,除了读取 b
的值将取决于如何编译器优化。但那是因为你的例子也写了。
主流 CPU 没有硬件竞争检测,所以它实际上不会出错,如果你真的等待 flag==1
然后只是读取,你会看到预期的值,即使作者被 运行 次相同值的赋值。 (DeathStation 9000 可以通过在 space 中临时存储其他内容来实现这些分配,因此内存中的字节实际上在变化,而不是第一次发布存储之前值的稳定副本,但这不是你想要的期待一个真正的编译器来做。不过我不会打赌,这似乎是一种反模式)。
这就是无锁队列使用多个数组元素,或者 seqlock 不能以这种方式工作的原因。 (一个 因为它依赖于读取可能撕裂的数据然后检测撕裂;如果你对数据块使用足够窄的松散原子,你会损害效率。)
可能在 reader 读完之前想要再次写作的整个想法听起来很像您应该研究 SeqLock 的想法。 https://en.wikipedia.org/wiki/Seqlock 并在最后一段中查看我的链接答案中的其他链接。
所以说我有这个构造,其中两个任务 运行 同时(伪代码):
int a, b, c;
std::atomic<bool> flag;
TaskA()
{
while (1) {
a = 5;
b = 2;
c = 3;
flag.store(true, std::memory_order_release);
}
}
TaskB()
{
while (1) {
flag.load(std::memory_order_acquire);
b = a;
c = 2*b;
}
}
内存屏障应该在标志变量处。据我了解,这意味着任务 B 中的操作(b = a 和 c = 2b)在任务 A 中的赋值(a = 5、b = 2、c = 3)之后执行。但这是否也意味着,当 TaskB 仍然处于 c = 2*b 时,TaskA 不可能已经循环并再次执行 b = 2 ?这是否以某种方式被阻止了,我是否需要在循环开始时设置第二个障碍?
如果您在发布存储后立即开始另一次非原子变量写入,再多的障碍也无法帮助您避免数据争用 UB。
当您的 reader 正在读取这些变量,因此在 C 抽象机中你有数据争用 UB。 (在您的示例中,来自 a
的非同步写入+读取、b
的非同步写入+写入以及 b
的写入+读取以及 c
的写入+写入.)
此外,即使 没有 循环,您的示例仍然无法安全地避免数据争用 UB,因为您的 TaskB 访问 a
、b
和c
无条件后flag.load
。因此,无论您是否观察到作者发出的 data_ready = 1 信号,表示变量已准备好被读取,您都会执行这些操作。
当然,在实际实现的实践中,重复写入 相同的 数据不太可能在这里引起问题,除了读取 b
的值将取决于如何编译器优化。但那是因为你的例子也写了。
主流 CPU 没有硬件竞争检测,所以它实际上不会出错,如果你真的等待 flag==1
然后只是读取,你会看到预期的值,即使作者被 运行 次相同值的赋值。 (DeathStation 9000 可以通过在 space 中临时存储其他内容来实现这些分配,因此内存中的字节实际上在变化,而不是第一次发布存储之前值的稳定副本,但这不是你想要的期待一个真正的编译器来做。不过我不会打赌,这似乎是一种反模式)。
这就是无锁队列使用多个数组元素,或者 seqlock 不能以这种方式工作的原因。 (一个
可能在 reader 读完之前想要再次写作的整个想法听起来很像您应该研究 SeqLock 的想法。 https://en.wikipedia.org/wiki/Seqlock 并在最后一段中查看我的链接答案中的其他链接。