阅读this关于C ++中双重检查锁定模式的文章,我到达了那里(第10页),作者展示了尝试正确实施DCLP""使用volatile
变量:
class Singleton {
public:
static volatile Singleton* volatile instance();
private:
static volatile Singleton* volatile pInstance;
};
// from the implementation file
volatile Singleton* volatile Singleton::pInstance = 0;
volatile Singleton* volatile Singleton::instance() {
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
volatile Singleton* volatile temp = new Singleton;
pInstance = temp;
}
}
return pInstance;
}
在这样的示例后,有一个我不明白的文本片段:
首先,标准对可观察行为的限制仅适用于 标准定义的抽象机器和抽象机器 没有多线程执行的概念。结果,尽管如此 标准防止编译器重新排序读取和写入 一个线程中的易失性数据,它根本没有施加任何约束 这样的重新排序跨线程。至少那是大多数编译器的方式 实施者解释事物。结果,在实践中,很多 编译器可能会从上面的源代码生成线程不安全的代码。
以后:
... C ++的抽象机器是单线程的,而C ++编译器可能 选择从源代码生成线程不安全的代码,如上所述, 反正。
这些评论与单处理器上的执行有关,所以它绝对不是关于缓存一致性的问题。
如果编译器无法对线程中的易失性数据进行读取和写入,那么如何针对此特定示例重新排序跨线程的读取和写入生成线程不安全的代码?
答案 0 :(得分:1)
我认为它们指的是第6节(“多处理器机器上的DCLP”)中讨论的缓存一致性问题。对于多处理器系统,处理器/缓存硬件可能会写出{的值{1}}在为已分配的Singleton
写出值之前。这可能导致第二个CPU在看到它指向的数据之前看到非NULL pInstance
。
这需要硬件围栏指令,以确保在系统中的其他CPU可以看到任何内存之前更新所有内存。
答案 1 :(得分:1)
指向Singleton的指针可能是易失性的,但单例内的数据不是。
想象一下,Singleton有int x, y, z;
个成员,出于某种原因在构造函数中设置为15, 16, 17
。
volatile Singleton* volatile temp = new Singleton;
pInstance = temp;
好的,temp
写在pInstance
之前。 x,y,z
什么时候写的?之前?后?你不知道。它们不是易失性的,因此不需要相对于易失性排序进行排序。
现在有一个帖子进来看到:
if (pInstance == 0) { // first check
让我们说pInstance
已设置,不为空。
x,y,z
的价值是多少?即使已调用new Singleton
,并且构造函数已“运行”,您也不知道设置x,y,z
的操作是否已运行。
所以现在你的代码去了x,y,z
并且崩溃,因为它真的期待15,16,17
,而不是随机数据。
哦等等,pInstance
是指向易失性数据的易失性指针!那么x,y,z
是不是很不稳定?对?因此与pInstance
和temp
订购。 AHA!
几乎。来自*pInstance
的任何读取都将是易变的,但通过new Singleton
的构造不是易变的。因此,对x,y,z
的初始写入没有被排序。 : - (
所以你可以,可能,让成员volatile int x, y, z;
好。然而...
C ++ 现在有一个内存模型,即使在撰写文章时没有。根据现行规则,volatile
不会阻止数据争用。 volatile
与线程无关。该计划是UB。猫和狗一起生活。
此外,尽管这推动了标准的限制(即,volatile
真正意味着什么变得模糊),一个全知,全视,全程序优化的编译器可以看看你的使用volatile
并说“不,那些挥发物实际上没有连接到任何IO存储器地址等,它们实际上不是可观察的行为,我只是让它们变得非易失性”...... < / p>
答案 2 :(得分:0)
如果我理解正确,他们会说在单线程抽象机器的上下文中,编译器可能只是转换:
volatile Singleton* volatile temp = new Singleton;
pInstance = temp;
分为:
pInstance = new Singleton;
因为可观察的行为没有改变。然后,这将我们带回到双重检查锁定的原始问题。