据我所知,编译器永远不会优化声明为volatile的变量。但是,我有这样声明的数组。

volatile long array[8];

并且不同的线程对其进行读写。数组的元素只能由其中一个线程修改,并可以由任何其他线程读取。但是,在某些情况下,我注意到即使修改线程中的元素,读取该元素的线程也不会注意到更改。它继续读取相同的旧值,就好像编译器已将其缓存在某个地方一样。但是原则上编译器不应该缓存一个volatile变量,对吗?那么这是怎么发生的。

注意:我没有使用volatile进行线程同步,所以请不要再给我答案,例如使用锁或原子变量。我知道volatile,原子变量和互斥锁之间的区别。另请注意,该架构是具有主动缓存一致性的x86。同样,在变量被另一个线程修改后,我读了足够长的时间。即使经过很长时间,读取线程也看不到修改后的值。

最佳答案



不,原则上,每次读取/写入变量时,编译器都必须读取/写入变量的地址。

[编辑:至少,它必须这样做,直到实现认为该地址上的值是“可观察的”。正如Dietmar在他的回答中指出的,一个实现可能会声明“无法观察到”正常内存。对于使用调试器,mprotect或其他超出标准范围的人员,这将是一个惊喜,但在原则上可以做到这一点。

在根本不考虑线程的C++ 03中,由实现来定义在线程中运行时“访问地址”的含义。这样的细节称为“内存模型”。例如,Pthread允许对整个内存(包括 volatile 变量)进行每个线程的缓存。 IIRC,MSVC确保适当大小的易失变量是原子的,并且将避免缓存(相反,它将刷新至所有内核的单个连贯缓存)。之所以提供这种保证,是因为在Intel上这样做相当便宜-Windows只真正在乎基于Intel的体系结构,而Posix则在关注它自己更多的奇特事物。

C++ 11定义了一个用于线程的内存模型,并说这是一场数据竞赛(即volatile不能确保一个线程中的读取相对于另一个线程中的写入是顺序进行的)。可以按特定顺序对两个访问进行排序,可以按未指定的顺序对两个访问进行排序(我不记得标准可能会说“不确定的顺序”),也可以根本不对两个访问进行排序。完全不排序是不好的-如果两个未排序的访问之一是写操作,则行为是不确定的。

这里的关键是“我修改了线程中的元素,然后读取它的线程没有注意到更改”中的隐含的“然后是”。您假设操作是有序的,但不是。就读取线程而言,除非您使用某种同步方式,否则不一定要在另一个线程中进行写操作。实际上,这还比这更糟。您可能会从我刚刚写的内容中认为,只是未指定操作的顺序,但是实际上,具有数据争用的程序的行为是不确定的。

09-30 14:48
查看更多