x86上的竞争条件

有人可以解释这个说法:

shared variables x = 0, y = 0 Core 1 Core 2 x = 1; y = 1; r1 = y; r2 = x; 

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

来自Bartosz Milewski的“并发语言” 。

由于涉及重新排序指令的优化,可能会出现问题。 换句话说,两个处理器可以分配变量xy 之前分配r1r2 ,如果它们发现这会产生更好的性能。 这可以通过添加内存屏障来解决,这将强制执行排序约束。

引用你在post中提到的幻灯片 :

现代多核/语言打破了顺序一致性

关于x86架构,最佳资源是英特尔®64和IA-32架构软件开发人员手册 (第8.2内存排序 )。 8.2.1和8.2.2节描述了由Intel486,Pentium,Intel Core 2 Duo,Intel Atom,Intel Core Duo,Pentium 4,Intel Xeon和P6系列处理器实现的内存排序 :称为处理器排序的内存模型,如反对较旧的Intel386架构的程序排序强排序 )(其中读和写指令总是按它们出现在指令流中的顺序发出)。

该手册描述了处理器订购内存模型的许多订购保证(例如, 负载不与其他负载重新排序存储不与其他存储重新排序存储不与旧负载重新排序等),但它还描述了允许的重新排序规则这导致OP的post中的竞争条件:

8.2.3.4载荷可以与较早的商店重新排序到不同的地点

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

 shared variables x = 0, y = 0 Core 1 Core 2 r1 = y; r2 = x; x = 1; y = 1; 

在这种情况下,处理器保证r1 = 1并且不允许r2 = 1情况(由于8.2.3.3存储没有按照早期负载保证重新排序),这意味着这些指令永远不会在各个内核中重新排序。

要将其与不同的体系结构进行比较,请查看本文: 现代微处理器中的内存排序 (具体为此图像 )。 您可以看到Itanium(IA-64)比IA-32架构更加重新排序。

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

这就是为什么有人说: 线程被认为是有害的

问题是两个线程都没有强制执行它们的两个语句之间的任何排序,因为它们不是相互依赖的。

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

  • CPU知道xy没有别名,因此可能会对它们进行重新排序以提高速度。 发生这种情况的一个很好的例子是当CPU检测到写入合并的机会时。 如果它可以这样做而不违反其一致性模型,它可以将一个写入与另一个写入合并。

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