Alb*_*das 9 c++ concurrency caching atomic c++17
我已经听并阅读了几篇关于 的文章、演讲和 stackoverflow 问题std::atomic,我想确定我已经很好地理解了它。因为由于 MESI(或派生)缓存一致性协议、存储缓冲区、无效队列等可能存在延迟,我仍然对缓存行写入可见性感到困惑。
我读到 x86 具有更强的内存模型,如果缓存失效延迟,x86 可以恢复已启动的操作。但我现在只对作为 C++ 程序员应该假设的内容感兴趣,而与平台无关。
[T1:线程1 T2:线程2 V1:共享原子变量]
我知道 std::atomic 保证,
(1) 变量上不会发生数据竞争(由于对缓存行的独占访问)。
(2) 根据我们使用的 memory_order,它保证(使用屏障)顺序一致性发生(在屏障之前、屏障之后或两者兼而有之)。
(3) 在T1 上的原子写(V1) 之后,T2 上的原子RMW(V1) 将是一致的(它的缓存行将已用T1 上的写入值更新)。
但正如缓存一致性入门所提到的,
所有这些事情的含义是,默认情况下,加载可以获取陈旧数据(如果相应的失效请求位于失效队列中)
那么,以下说法正确吗?
(4)std::atomic不保证 T2 在 T1 上的原子写(V)之后不会在原子读(V)上读取“陈旧”值。
问题(4)是否正确:如果无论延迟如何,T1 上的原子写入都会使缓存行失效,那么为什么 T2 在执行原子 RMW 操作而不是原子读取时等待失效生效?
问题(4)是否错误:线程何时可以在执行中读取“过时”值并且“它是可见的”?
我非常感谢你的回答
更新 1
所以看来我在(3)上错了。想象以下交错,对于初始 V1=0:
T1: W(1)
T2: R(0) M(++) W(1)
Run Code Online (Sandbox Code Playgroud)
即使在这种情况下 T2 的 RMW 保证完全发生在 W(1) 之后,它仍然可以读取“陈旧”值(我错了)。据此,atomic 不保证完全缓存一致性,只保证顺序一致性。
更新 2
(5) 现在想象这个例子(x = y = 0 并且是原子的):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
Run Code Online (Sandbox Code Playgroud)
根据我们所说的,看到屏幕上显示的“msg”不会给我们提供除了 T2 在 T1 之后执行之外的信息。因此,可能发生了以下任一处决:
那正确吗?
(6) 如果一个线程总是可以读取 'stale' 值,如果我们采用典型的“发布”场景,而不是发出一些数据已准备就绪的信号,而是做相反的事情(删除数据),会发生什么?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
Run Code Online (Sandbox Code Playgroud)
其中 T2 仍将使用已删除的 ptr,直到看到 is_enabled 为 false。
(7) 此外,线程可能读取“过时”值这一事实意味着不能仅用一个无锁原子来实现互斥锁?它需要线程之间的同步机制。它需要一个可锁定的原子吗?
memory_order值可以保证顺序一致性原子读-修改-写操作以保证其原子性的方式指定。如果另一个线程可以在初始读取之后和 RMW 操作写入之前写入该值,则该操作将不是原子的。
线程总是可以读取过时的值,除非发生之前保证相对排序。
如果 RMW 操作读取“陈旧”值,则它保证它生成的写入在其他线程的任何写入(覆盖其读取的值)之前可见。
更新例如
如果 T1 写入x=1且 T2 写入x++,x初始值为 0,则从存储的角度来看,选择x是:
T1 的写入是第一个,因此 T1 写入x=1,然后 T2 读取x==1,将其增加到 2 并x=2作为单个原子操作写回。
T1的写入是第二。T2 读取,将其递增到 1,并作为单个操作x==0写回,然后 T1 写入。x=1x=1
但是,如果这两个线程之间没有其他同步点,则线程可以继续执行未刷新到内存的操作。
因此,T1 可以发出x=1,然后继续处理其他事情,即使 T2 仍然会读取x==0(并因此写入x=1)。
如果存在任何其他同步点,那么哪个线程x首先修改就会变得显而易见,因为这些同步点将强制执行顺序。
如果您对从 RMW 操作读取的值有条件,则这一点最为明显。
更新2
memory_order_seq_cst对所有原子操作使用(默认),则无需担心此类事情。从程序的角度来看,如果你看到“msg”,那么T1运行,然后T3,然后T2。如果您使用其他内存顺序(尤其是memory_order_relaxed),那么您可能会在代码中看到其他情况。
在这种情况下,你有一个错误。假设is_enabled标志为 true,当 T2 进入while循环时,它决定运行主体。T1 现在删除数据,然后 T2 引用指针(这是一个悬空指针),并随之发生未定义的行为。除了防止标志上的数据竞争之外,原子不会以任何方式帮助或阻碍。
您可以使用单个原子变量来实现互斥体。
| 归档时间: |
|
| 查看次数: |
436 次 |
| 最近记录: |