因此,假设我有一个构造,其中两个任务同时运行(伪代码):
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;
}
}内存屏障应该位于标志变量处。据我理解,这意味着TaskB中的操作(b =a和c= 2b)是在TaskA中的分配(a = 5,b= 2,c= 3)之后执行的。但这是否也意味着,当TaskA仍在c= 2*b时,TaskB不可能第二次循环执行b=2?这在某种程度上是被阻止的吗,我们的循环开始时需要第二个屏障吗?
发布于 2021-07-16 06:21:21
如果您在发布存储后立即开始另一次非原子变量的写入,那么任何障碍都不能帮助您避免数据竞争UB。
在读取器读取这些变量时,对a、b和c的非原子写入始终是可能的(而且很有可能),因此在C抽象机器中有数据争用UB。(在您的示例中,来自非同步的write+read of a、unsynced write+write of b、write+read of b和write+write of c)。
而且,即使没有循环,您的示例仍然无法安全地避免数据竞争UB,因为您的TaskB在flag.load之后无条件地访问a、b和c。因此,不管您是否观察到作者发出的data_ready =1信号,您都可以这样做,即vars已经准备好被读取了。
当然,在实际实现中,重复编写相同的数据不太可能在这里造成问题,但b的读取值将取决于编译器的优化方式。但那是因为你的例子也写了。
主流CPU没有硬件竞争检测,因此它实际上不会出现故障或其他什么,而且如果您确实等待flag==1,然后只读取,即使编写器运行了更多相同值的赋值,您也会看到预期的值。( DeathStation 9000可以通过临时在该空间存储其他东西来实现这些赋值,因此内存中的字节实际上正在改变,而不是第一个发行版存储之前值的稳定副本,但这不是真正的编译器应该做的事情。不过,我不敢打赌,这似乎是一种反模式)。
这就是为什么无锁队列使用多个数组元素,或者seqlock不能这样工作的原因。( seqlock can't be implemented both safely and efficiently in ISO C++是因为它依赖于读取可能被撕裂的数据,然后检测撕裂;如果您使用窄的、足够宽松的原子来处理数据块,则会影响效率。)
想要再次写作的整个想法,也许在读者完成阅读之前,听起来很像你应该研究SeqLock的想法。https://en.wikipedia.org/wiki/Seqlock并在最后一段中看到我链接的答案中的其他链接。
https://stackoverflow.com/questions/68404071
复制相似问题