双重检查锁定模式

时间:2011-02-19 12:26:19

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

C++ and the Perils of Double-Checked Locking中,有一些persudo代码可以正确地实现模式,这是作者建议的。见下文,

Singleton* Singleton::instance () {
    Singleton* tmp = pInstance;
    ... // insert memory barrier (1)
    if (tmp == 0) {
        Lock lock;
        tmp = pInstance;
        if (tmp == 0) {
            tmp = new Singleton;
            ... // insert memory barrier (2)
            pInstance = tmp;
        }
    }
    return tmp;
}

我只是想知道第一个内存屏障是否可以在return语句的正上方移动?

编辑:另一个问题:在链接文章中,引用vidstige

  

从技术上讲,您不需要完全双向障碍。第一道屏障   必须防止单身人士的建设向下迁移   (通过另一个线程);第二道屏障必须防止向上迁移   pInstance的初始化。这些被称为“获得”和   “释放”操作,可能会产生比完整更好的性能   硬件上的障碍(例如Itainum)作出区分。

它说第二个屏障不需要是双向的,那么如何防止pInstance的赋值在该屏障之前被移动?即使第一道屏障可以阻止向上迁移,但另一个线程仍然有机会看到未初始化的成员。

编辑我想我几乎理解了第一道屏障的目的。正如sonicoder所指出的,当if返回true时,分支预测可能导致tmp为NULL。为了避免这个问题,必须有一个获取障碍,以防止在读取if之前读取tmp。

第一个屏障与第二个屏障配对以实现同步关系,因此可以向下移动。

编辑:对于对此问题感兴趣的人,我强烈建议您阅读memory-barriers.txt

2 个答案:

答案 0 :(得分:5)

我没有看到任何与您的问题相关的正确答案,所以我决定在三年多之后发布一个;)

  

我只是想知道第一个记忆障碍是否能够正确移动   在返回声明之上?

是的,它可以。

对于不会进入if语句的线程,即pInstance已经构建并正确初始化,并且可见。

第二个屏障(pInstance = tmp;之前的屏障)保证在提交pInstance = tmp;之前将单例成员字段的初始化提交到内存。 但这并不一定意味着其他线程(在其他核心上)会以相同的顺序看到这些记忆效应 (反直觉,对吧?)。第二个线程可能会在缓存中看到指针的新值,但尚未看到那些成员字段。当它通过解除引用指针(例如,p->data)来访问成员时,该成员的地址可能已经在高速缓存中,但不是所需的地址。砰!读取错误的数据。请注意,这不仅仅是理论上的。 There are systems您需要执行缓存一致性指令(例如,内存屏障)以从内存中提取新数据。

这就是为什么第一道屏障就在那里。它还解释了为什么可以在return语句之前放置它(但它必须在Singleton* tmp = pInstance;之后。)

  

它说第二道屏障不需要是双向的,所以   它如何防止pInstance的赋值被移动之前   那障碍?

写屏障保证 在它之后的每次写入之前的每次写入都会有效地发生 。这是一个停止标志,没有写入可以将它交叉到另一边。有关更详细的说明,请参阅here

答案 1 :(得分:4)

不,内存屏障不能移动到赋值语句之下,因为内存屏障可以保护分配不被向上迁移。来自链接的文章:

  

第一道屏障必须防止   单身人士的向下迁移   建筑(由另一个线程);该   第二道屏障必须防止向上   pInstance的迁移   初始化。

侧面说明:单一的双重检查锁定模式仅在您有巨大的性能要求时才有用。

你有没有想过你的二进制文件并将单身人士访问视为瓶颈?如果没有机会你不需要打扰双重锁定模式。

我建议使用简单的锁。