This tutorial说以下内容:
x86 / 64上的每个加载已经暗示了获取语义和每个商店 暗示释放语义。
现在说我有以下代码(我在评论中写了我的问题):
/* Global Variables */
int flag = 0;
int number1;
int number2;
//------------------------------------
/* Thread A */
number1 = 12345;
number2 = 678910;
flag = 1; /* This is a "store", so can I not use a release barrier here? */
//------------------------------------
/* Thread B */
while (flag == 0) {} /* This is a "load", so can I not use an acquire barrier here? */
printf("%d", number1);
printf("%d", number2);
答案 0 :(得分:3)
本教程在程序集/计算机级别讨论装载和存储,您可以依赖x86 ISA语义,包括load-on-load和release -on-存储。
然而,您的代码是C
,它根本不提供此类保证,编译器可以自由地将其转换为与您在加载和存储方面所期望的完全不同的内容。这不是理论上的,它在实践中发生。所以简短的回答是:不,不可能在C语言中合法地进行 - 尽管如果你运气好的话可能会有效。
答案 1 :(得分:1)
假设代码是用C99编写的,目标架构是x86。 x86的强大内存模型仅在机器代码级别生效。 C99没有内存模型。我将解释可能出现的问题并讨论是否存在符合C99标准的处理问题的方法。
首先,我们必须确保没有任何变量得到优化,并且对flag
,number1
和number2
的所有访问都是从内存发生的,而不是在CPU寄存器中缓存 1 。这可以通过使用volatile
对所有三个变量进行限定来在C99中实现。
其次,我们必须确保第一个线程中的flag
存储具有释放语义。这些语义包括两个保证:到flag
的存储不会与先前的内存访问重新排序,并使存储对第二个线程可见。 volatile
关键字告诉编译器访问变量可能会产生可观察到的副作用。这可以防止编译器针对其他操作重新排序对volatile变量的访问,这些操作也被编译器视为具有可观察到的副作用。也就是说,通过创建所有三个变量volatile
,编译器将保持第一个线程中所有三个存储的顺序。也就是说,如果存储在flag
的存储之上或之下的其他非易失性存储器访问,则仍然可以重新排序这些访问。因此标准volatile
仅提供部分释放语义。
第三......实际上,对于您的特定代码,不需要原子性。那是因为flag
的商店只改变了一位,这本身就是原子的。因此,对于这个特定代码,您不必担心原子性。但一般来说,如果flag
的存储可能会改变多于一位,并且如果在第二个线程中检查的条件可能会有不同的行为,具体取决于它是否看到全部或部分位更改,那么您肯定需要确保对'flag`的访问是原子的。不幸的是,C99没有原子性的概念。
要获得完整的发布语义和原子性,您可以使用C11原子(如您引用的文章中所讨论的),也可以使用特定于编译器的技术(也在您引用的文章中讨论过)。当然,您仍然可以查看生成的机器代码,看看x86内存模型本身是否提供了正确性的必要要求。这在大型代码库中是不可行的。此外,下次编译代码时,生成的机器代码可能会更改。最后,因为你只是一个人,你可能会犯错。
(1)在引用的文章中,变量A
被声明为共享的全局变量。现在很可能编译器会从内存中分配它。但这严格符合标准吗?什么阻止编译器在程序的整个生命周期中将它分配到寄存器中?不确定。