c++ - 使用 volatile 在 C++ 98/03 中实现双重检查锁定

标签 c++ multithreading singleton double-checked-locking

阅读 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 是可变的吧?正确的?因此使用 pInstancetemp 进行排序。啊哈!

差不多。任何来自 *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/

相关文章:

c++ - 模板概念是否达到了 c++14?

c++ - super 强大:实时音调变换,时间拉伸(stretch)器不工作

multithreading - 波尔多线程 : how to kill a thread?

javascript - 什么是单例类型数组?

java - 如何在java中重新创建单例类

c++ - 点到线/段的距离

c++ - gcc 是否优化递归函数?怎么做?

c# - 多线程 InvalidOperationException C#

java并发,生产者(经纪人)和消费者

spring - 如何在 Grails 中获取原型(prototype) bean 的实例?