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 访问 abc无条件后flag.load。因此,无论您是否观察到作者发出的 data_ready = 1 信号,表示变量已准备好被读取,您都会执行这些操作。

当然,在实际实现的实践中,重复写入 相同的 数据不太可能在这里引起问题,除了读取 b 的值将取决于如何编译器优化。但那是因为你的例子也写了。

主流 CPU 没有硬件竞争检测,所以它实际上不会出错,如果你真的等待 flag==1 然后只是读取,你会看到预期的值,即使作者被 运行 次相同值的赋值。 (DeathStation 9000 可以通过在 space 中临时存储其他内容来实现这些分配,因此内存中的字节实际上在变化,而不是第一次发布存储之前值的稳定副本,但这不是你想要的期待一个真正的编译器来做。不过我不会打赌,这似乎是一种反模式)。


这就是无锁队列使用多个数组元素,或者 seqlock 不能以这种方式工作的原因。 (一个 因为它依赖于读取可能撕裂的数据然后检测撕裂;如果你对数据块使用足够窄的松散原子,你会损害效率。)

可能在 reader 读完之前想要再次写作的整个想法听起来很像您应该研究 SeqLock 的想法。 https://en.wikipedia.org/wiki/Seqlock 并在最后一段中查看我的链接答案中的其他链接。