ash*_*hen 12 c++ multithreading double-checked-locking
在C++和Double-Checked Locking的Perils中,有一些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;
}
Run Code Online (Sandbox Code Playgroud)
我只是想知道第一个内存屏障是否可以在return语句的正上方移动?
编辑:另一个问题:在链接文章中,引用vidstige
从技术上讲,您不需要完全双向障碍.第一道屏障必须防止Singleton构造的向下迁移(通过另一个线程); 第二个障碍必须阻止pInstance初始化的向上迁移.这些被称为"获取"和"释放"操作,并且可以产生比硬件(例如Itainum)上的完全障碍更好的性能.
它说第二个障碍不需要是双向的,那么如何防止pInstance的赋值在该障碍之前被移动?即使第一个障碍可以阻止向上迁移,但另一个线程仍然有机会看到未初始化的成员.
编辑:我想我几乎明白第一道屏障的目的.正如sonicoder所指出的,当if返回true时,分支预测可能导致tmp为NULL.为了避免这个问题,必须有一个获取障碍,以防止在读取if之前读取tmp.
第一道屏障与第二道屏障配对以实现同步关系,因此它可以向下移动.
编辑:对于那些对这个问题感兴趣的人,我强烈建议阅读memory-barriers.txt.
我没有看到任何与你的问题有关的正确答案所以我决定在三年多之后发布一个;)
我只是想知道第一个内存屏障是否可以在return语句的正上方移动?
是的,它可以.
它适用于不会进入if
语句的线程,即pInstance
已经正确构造和初始化的线程,并且是可见的.
第二个障碍(前一个障碍pInstance = tmp;
)保证了singleton的成员字段的初始化在提交之前pInstance = tmp;
被提交到内存.但这并不一定意味着其他线程(在其他核心上)将以相同的顺序看到这些记忆效应(反直觉,对吧?).第二个线程可能会在缓存中看到指针的新值,但尚未看到那些成员字段.当它通过解除引用指针(例如p->data
)来访问成员时,该成员的地址可能已经在缓存中,但不是所需的地址.砰! 读取错误的数据.请注意,这不仅仅是理论上的.有些系统需要执行缓存一致性指令(例如,内存屏障)以从内存中提取新数据.
这就是为什么第一道屏障就在那里.它还解释了为什么可以在return
声明之前放置它(但它必须在之后Singleton* tmp = pInstance;
).
它说第二个障碍不需要是双向的,那么如何防止pInstance的赋值在该障碍之前被移动?
写屏障保证其前面的每个写操作都会在每次写入之前有效地发生.这是一个停止标志,没有任何写入可以跨越到另一边.有关更详细的说明,请参阅此处.