阅读 this关于 C++ 中的双重检查锁定模式的文章,我到达了作者演示使用 volatile
变量“正确”实现 DCLP 的尝试之一的地方(第 10 页):
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;
}
在这样的例子之后有一个我不明白的文本片段:
First, the Standard’s constraints on observable behavior are only for an abstract machine defined by the Standard, and that abstract machine has no notion of multiple threads of execution. As a result, though the Standard prevents compilers from reordering reads and writes to volatile data within a thread, it imposes no constraints at all on such reorderings across threads. At least that’s how most compiler implementers interpret things. As a result, in practice, many compilers may generate thread-unsafe code from the source above.
及以后:
... C++’s abstract machine is single-threaded, and C++ compilers may choose to generate thread-unsafe code from source like the above, anyway.
这些评论与单处理器上的执行有关,因此绝对与缓存一致性问题无关。
如果编译器无法在一个线程内重新排序读取和写入 volatile 数据,它如何为这个特定示例重新排序跨线程的读取和写入,从而生成线程-不安全的代码?
最佳答案
指向单例的指针可能是易变的,但单例内的数据不是。
假设 Singleton 有 int x, y, z;
作为成员,出于某种原因在构造函数中设置为 15, 16, 17
。
volatile Singleton* volatile temp = new Singleton;
pInstance = temp;
OK,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
进行排序。啊哈!
差不多。任何来自 *pInstance
的读取都将是易变的,但通过 new Singleton
的构造不是易变的。所以对 x,y,z
的初始写入没有排序。 :-(
所以您可以,也许,让成员volatile int x, y, z;
OK。然而……
C++ 现在 有内存模型,即使在撰写本文时它没有。根据当前规则,volatile
不能防止数据竞争。 volatile
与线程无关。该程序是 UB。猫和狗住在一起。
此外,尽管这正在插入标准的极限(即 volatile
的真正含义变得模糊),但一个无所不知、无所不见、全程序优化的编译器可以查看您对 volatile
的使用并说“不,那些 volatiles 实际上并没有连接到任何 IO 内存地址等,它们确实不是可观察到的行为,我只是想让它们成为非 volatile 的”...
关于c++ - 使用 volatile 在 C++ 98/03 中实现双重检查锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39454611/