双重检查锁定模式 [英] double checked locking pattern

查看:162
本文介绍了双重检查锁定模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++和的危险双重检查锁定,有persudo代码正确地执行该模式是由建议作者。

  Singleton * Singleton :: instance(){
Singleton * tmp = pInstance;
... //插入内存屏障(1)
if(tmp == 0){
锁定锁;
tmp = pInstance;
if(tmp == 0){
tmp = new Singleton;
... //插入内存屏障(2)
pInstance = tmp;
}
}
return tmp;
}

我只是想知道第一个内存屏障是否可以移回声明?



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


从技术上讲,您不需要完全的双向障碍。第一个障碍
必须防止Singleton的构造
(由另一个线程)向下迁移;第二个障碍必须阻止向上迁移
的pInstance的初始化。这些被称为获取和
释放操作,可能会产生比硬件(如Itainum)满
壁垒更好的性能,使区别。




它说,第二个障碍并不需要是双向的,所以它怎么能阻止这道屏障前被移动分配到pInstance? 。即使第一道屏障可以阻止向上迁移,但另一个线程仍然可以有机会看到未初始化成员



修改:<罢工>我想我几乎明白第一道障碍的目的。如 sonicoder 所述,当if返回true时,分支预测可能会导致tmp为NULL。为了避免这个问题,必须有一个获取屏障,以防止在读取if之前返回读取tmp。



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

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

解决方案

p>我没有看到任何正确的答案在这里与你的问题,所以我决定即使经过三年多发布一个;)




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




这是用于不会进入 if 语句的线程,即 pInstance



第二个障碍( pInstance = 之前的一个障碍) tmp; )确保在提交 pInstance = tmp; 之前,singleton的成员字段的初始化提交到内存。 但是,这并不意味着其他线程(在其它核心)会看到相同的顺序(反直觉的,对不对?)这些记忆效应。第二个线程可以看到缓存中的指针的新值,但是还没有看到那些成员字段。当它被取消引用指针访问一个成员(例如, P->数据),该成员的地址可能已经处于高速缓存中,而不是一个最想要的。砰!读取错误的数据。注意,这不仅仅是理论。 有系统中,你需要执行高速缓存一致性指令(例如,内存屏障)来从内存中提取新数据。



这就是为什么第一个障碍是存在的。它也解释了为什么它放在它之前的 return 语句(但它必须在 Singleton * tmp = pInstance; )。


它说第二个障碍不需要是双向的,所以
怎么能防止


写入屏障确保 每个写之前它将有效地发生之前每次写之后 。这是一个停车标志,没有写可以交叉到另一边。有关详细说明,请参阅此处


In C++ and the Perils of Double-Checked Locking, there's persudo code to implement the pattern correctly which is suggested by the authors. See below,

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;
}

I just wonder that whether the first memory barrier can be moved right above the return statement?

EDIT: Another question: In the linked article, as vidstige quoted

Technically, you don’t need full bidirectional barriers. The first barrier must prevent downwards migration of Singleton’s construction (by another thread); the second barrier must prevent upwards migration of pInstance’s initialization. These are called "acquire" and "release" operations, and may yield better performance than full barriers on hardware (such as Itainum) that makes the distinction.

It says that the second barrier doesn't need to be bidirectional, so how can it prevent the assignment to pInstance from being moved before that barrier? Even though the first barrier can prevent upwards migration, but another thread can still have chance to see the un-initialized members.

EDIT: I think I almost understand the purpose of the first barrier. As sonicoder noted, branch prediction may cause tmp to be NULL when the if returns true. To avoid that problem, there must be a acquire barrier to prevent the reading of tmp in return before the reading in if.

The first barrier is paired with the second barrier to achieve synchronize-with relationship, so it can be move down.

EDIT: For those who are interested in this question, I strongly recommend reading memory-barriers.txt.

解决方案

I didn't see any correct answer here related to your question so I decide to post one even after more than three years;)

I just wonder that whether the first memory barrier can be moved right above the return statement?

Yes, it can.

It's for threads that won't enter the if statement, i.e., pInstance has already been constructed and initialized correctly, and is visible.

The second barrier (the one right before pInstance = tmp;) guarantees that the initialization of singleton's members fields are committed to memory before pInstance = tmp; is committed. But this does NOT necessarily mean that other threads (on other cores) will see these memory effects in the same order (counter-intuitive, right?). A second thread may see the new value of the pointer in cache but not those member fields yet. When it accesses a member by dereferencing the pointer (e.g., p->data), the address of that member may has already been in cache, but not the one that's desired. Bang! A wrong data is read. Note that this is more than theoretical. There are systems that you need perform a cache coherence instruction (e.g., a memory barrier) to pull new data from memory.

That's why the first barrier is there. It also explains why it's ok to place it right before the return statement (but it has to be after Singleton* tmp = pInstance;).

It says that the second barrier doesn't need to be bidirectional, so how can it prevent the assignment to pInstance from being moved before that barrier?

A write barrier guarantees that every write preceding it will effectively happen before every write following it. It's a stop sign, and no write can cross it to the other side. For a more detailed description, refer to here.

这篇关于双重检查锁定模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆