我正在阅读内存一致性模型资料,我遇到了两个程序示例。我不确定我的理解是否正确以及强调理由。
一般问题是: 函数调用use()中的数据可以用作0吗?
计划1
int data = 0, ready = 0;
void p1 (void *ignored) {
data = 2000;
ready = 1;
}
void p2 (void *ignored) {
while (!ready)
;
use (data);
}
我认为数据在p2()中使用时必须是2000,因为数据和ready在p1()中有商店排序。
计划2
int a = 0, b = 0;
void p1 (void *ignored) { a = 1; }
void p2 (void *ignored) {
if (a == 1)
b = 1;
}
void p3 (void *ignored) {
if (b == 1)
use (a);
}
我认为a必须在p3()中用作1,因为在p3()中,除非b == 1,否则不会使用。在p2()中,除非a == 1,否则b不会被存储。因此当在p2中使用a时,a必须为1。
我的理解是否正确?
我考虑使用具有3级缓存的Intel Haswell处理器。让我们考虑两种情况:NUMA和UMA。
是的,我可以创建一个多线程程序来测试它,但我更愿意理解它的工作原理以及为什么它在理论上没有,这样我才能理解这个事实背后的秘密。 :-D
[另一个答案] 如果我们考虑英特尔处理器中的读取预取和高速缓存一致性模型,那么在将数据作为1存储在另一个核心上并通过高速缓存控制器标记为无效之前,一个线程可能从其私有高速缓存中预取变量a是可能的。 。在这种情况下,两个程序都可以将变量数据用作1.在UMA和NUMA模型下可能是相同的情况。
非常感谢你的帮助!
答案 0 :(得分:1)
计划1
如果那是文字C,而不是伪代码,那么:
p1
at compile time。while (!ready);
不是ready
,编译器可以自由地提升volatile
的负载。 (因此循环总是运行零或无穷大。)正常的方法是使用C11 atomic_load_explicit(&ready, memory_order_acquire)
。 (在x86上,每个负载都是一个load-acquire,因此它是免费的。以这种方式而不是*(volatile int*)&ready
编写它会使你的代码可以移植到任何C11实现,无论架构如何。)您错误地认为针对强排序ISA的C实现在源级别具有强排序。 C程序以C抽象机为目标。编译器生成可执行代码,生成结果就像它在抽象机器上运行C源代码一样,具有抽象机器的内存排序规则。在前面的段落中查看Jeff Preshing博客的链接。
计划2
只要p3
中的负载是获取负载,那么是的,你的推理是合理的。 (在x86上,这是免费发生的,并且使用类似的代码,编译时推测性重新排序不太可能产生行为不同的代码。但有可能:通常允许值推测。)
我不确定b=1
中的p2
商店是否需要成为发布商店。我是这么认为的,否则在一个弱有序的系统上,它可能会在发现a==1
的负载之前变得全局可见。 (同样,这在x86上是免费的。)
我正在考虑具有3级缓存的Intel Haswell处理器。让我们考虑两种情况:NUMA和UMA。
NUMA不影响ISA的订购保证。它可能使现有的单核CPU在实践中不会发生更有可能或更有可能的重新排序。 (虽然注意超线程是一种NUMA,因为共享相同逻辑核心的线程比其他核心更快地看到彼此的内存访问)。
在NUMA系统上中断的代码在任何系统上都被破坏,处理,并且不应该被信任。
如果您正在撰写新代码,请使用C11原子。您需要一些东西来阻止编译时重新排序/提升,而C11 stdatomic或等效的C ++ 11 std::atomic
是现代的方法。
您的代码不仅会避免任何编译器特定的编译器障碍(防止重新排序),您的代码将根据它实际依赖的内存排序要求进行自我记录。它甚至可以移植到ARM或任何其他架构,因为它明确地在需要时使用了acquire-loads,并在需要的地方使用了release-store。
原子类型的默认排序是memory_order_seq_cst
,因此您经常需要包含商店的显式排序版本的函数,以阻止它们在您不在时发出完整内存屏障的指令需要它(x86上的mfence
)。 x86原子读 - 修改 - 写总是必须使用lock
前缀,所以在x86上,对于比mo_seq_cst
更弱的排序没有任何好处,但使用最弱的排序使得你的算法没有坏处正确。 (除非您无法在x86硬件上进行测试,看看您是否使用了太弱的订购)。
e.g。 my_var = 1
将汇编为mov [my_var], 1
/ mfence
,因此您必须使用atomic_store_explicit( &my_var, 1, memory_order_release )
来{{1}}。