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

时间:2016-09-12 16:14:00

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

阅读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 ++编译器可能   选择从源代码生成线程不安全的代码,如上所述,   反正。

这些评论与单处理器上的执行有关,所以它绝对不是关于缓存一致性的问题。

如果编译器无法对线程中的易失性数据进行读取和写入,那么如何针对此特定示例重新排序线程的读取和写入生成线程不安全的代码?

3 个答案:

答案 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是不是很不稳定?对?因此与pInstancetemp订购。 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;

因为可观察的行为没有改变。然后,这将我们带回到双重检查锁定的原始问题。