x86上的种族条件

问题描述 投票:24回答:3

有人可以解释这个说法:

shared variables
x = 0, y = 0

Core 1       Core 2
x = 1;       y = 1;
r1 = y;      r2 = x;

如何在x86处理器上使用r1 == 0r2 == 0

来源"The Language of Concurrency" by Bartosz Milewski

c++ c x86 race-condition
3个回答
27
投票

由于涉及reordering of instructions的优化而出现问题。换句话说,两个处理器都可以分配r1r2 before分配变量xy,如果他们发现这样做会产生更好的性能。可以通过添加memory barrier来解决,该方法将强制执行排序约束。

引用您在帖子中提到的slideshow

现代多核/语言中断顺序一致性

关于x86架构,最好的读取资源是Intel® 64 and IA-32 Architectures Software Developer’s Manual(第8.2内存排序)。第8.2.1节和第8.2.2节描述了由Intel486,Pentium,Intel Core 2 Duo,Intel Atom,Intel Core Duo,Pentium 4,IntelXeon和P6家族处理器:一种称为处理器顺序的内存模型,与较旧的Intel386体系结构中的程序顺序强顺序)(始终在其中发出读写指令)相反它们在指令流中出现的顺序)。

该手册描述了处理器订购内存模型的许多订购保证(例如未与其他负荷一起对负荷进行重新排序未与其他商店进行负荷对存储进行重新排序与较早的负荷未对存储器进行重排等),但它也描述了允许的重新排序规则,该规则导致OP帖子中的竞争条件:

8.2.3.4可以将早期商店的负载重新排序为不同的负载位置

另一方面,如果指令的原始顺序已切换:

shared variables
x = 0, y = 0

Core 1       Core 2
r1 = y;      r2 = x;
x = 1;       y = 1;

在这种情况下,处理器保证不允许出现r1 = 1r2 = 1情况(由于8.2.3.3不对存储进行早期排序而重新排序保证),这意味着这些指令永远不会单独进行重新排序核心。

要将此与不同的体系结构进行比较,请查看本文:Memory Ordering in Modern Microprocessors。您可以看到Itanium(IA-64)的重排序比IA-32架构还要多:

Possible CPU reorderings for various architectures


3
投票

在具有较弱内存一致性模型的处理器(例如SPARC,PowerPC,Itanium,ARM等)上,可能会发生上述情况,因为在没有显式内存屏障指令的情况下写入时缺乏强制的缓存一致性。因此,基本上Core1x之前看到对y的写入,而Core2y之前看到了x的写入。在这种情况下,不需要完整的篱笆指令...基本上,您只需要在这种情况下强制执行写或释放语义,以便在对已被处理的变量进行读取之前,所有处理器都可以提交所有写入并对所有处理器可见写给。具有强大的内存一致性模型(例如x86)的处理器体系结构通常不需要这样做,但是正如Groo所指出的,编译器本身可以对操作进行重新排序。您可以在C和C ++中使用volatile关键字来防止编译器在给定线程中对操作进行重新排序。但这并不是说volatile将创建线程安全代码来管理线程之间读写的可见性……这将需要一个内存屏障。因此,尽管使用volatile仍然可以创建不安全的线程代码,但在给定的线程内,它将在已编译的机器代码级别上强制执行顺序一致性。


2
投票

这就是为什么有人说:Threads Considered Harmful

问题是,两个线程都不执行任何顺序,因为它们不是相互依赖的。

  • 编译器知道xy没有别名,因此不需要对操作进行排序。

  • CPU知道xy没有别名,因此可以为速度重新排序。一个很好的例子就是CPU检测到write combining的机会。如果它可以在不违反其一致性模型的情况下将一个写入与另一个写入合并。

相互依赖性看起来很奇怪,但实际上与其他任何竞争条件都没有什么不同。直接编写共享内存线程代码是非常困难的,这就是为什么开发并行语言和消息传递并行框架的原因,以便将并行危害隔离到小内核并从应用程序本身中消除危害。

© www.soinside.com 2019 - 2024. All rights reserved.