这种在C++11中实现双重检查锁模式(DCLP)是否正确
Is this implementation of Double checked lock pattern (DCLP) in C++11 is correct?
我正在读关于DCLP(双重检查锁模式(的文章,但我不确定我是否做对了。当使用原子创建锁时(如C++11中修复的DCLP中所解释的(,有两件事不清楚:
- 在文章的代码中:
std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; m_instance.store(tmp, std::memory_order_release); } } return tmp; }
如果我在"load(("中获取了围栏,但tmp不是nullptr,并且我只是返回,会发生什么?难道我们不应该说明CPU可以在哪里"释放围栏"吗?
如果不需要释放围栏,那为什么我们必须获得并释放?有什么区别?
我确实错过了一些基本的东西。。。。
- 如果我正确地理解了这篇文章,那么这也是实现DCLP的正确方法吗
Singleton* Singleton::m_instance = null; std::atomic<bool> Singleton::is_first; // init to false std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance() { bool tmp = is_first.load(std::memory_order_acquire); if (tmp == false) { std::lock_guard<std::mutex> lock(m_mutex); tmp = is_first.load(std::memory_order_relaxed); if (tmp == false) { // can place any code that will run exactly once! m_instance = new Singleton; // store back the tmp atomically is_first.store(tmp, std::memory_order_release); } } return m_instance; }
换句话说,我没有查看实例,而是使用原子布尔值来确保DCLP工作,并且第二个tmp中的任何内容都必须同步并运行一次。这是正确的吗?
谢谢!
EDIT:注意,我不是在问实现singleton的问题,只是为了更好地理解fence和atomic的概念,以及它是如何修复DCLP的。这是一个理论问题。
如果我在"load(("中获取了围栏,但tmp不是nullptr,并且我只是返回,会发生什么?难道我们不应该说明CPU可以在哪里"释放围栏"吗?
没有。当存储到m_instance
时,就完成了释放。如果你加载了m_instance
并且它不是null,那么发布已经提前了,你不需要这么做
您不会像获取互斥锁那样"获取围栏"answers"释放围栏"。围栏不是这样的。围栏只是一种获取或释放操作,没有相关的内存位置。围栏在这里并不真正相关,因为所有的获取和释放操作都有一个相关的内存位置(原子对象m_instance
(。
您不必像互斥锁+解锁那样在匹配的对中获得+释放。您可以执行一个释放操作来存储值,并进行任意数量的获取操作(零个或多个(来加载该值并观察其效果。
加载/存储的获取/释放语义与加载/存储两侧的操作顺序有关,以防止重新排序。
变量A的非松弛原子存储(即释放操作(将与相同变量A的后续非松弛原子加载(即获取操作(同步。
正如C++标准所说:
非正式地,在a上执行释放操作迫使前一方对其他内存位置的影响,使其对稍后在a.上执行消耗或获取操作的其他线程可见
因此,在您引用的DCLP代码中,m_instance.store(tmp, memory_order_release)
是m_instance
的存储,是一个发布操作。m_instance.load(memory_order_acquire)
是来自m_instance
的负载,并且是获取操作。内存模型表示,非空指针的存储与看到非空指针时的任何加载同步,这意味着在任何线程可以从tmp
加载非空值之前,可以保证new Singleton
的所有效果都已完成。这修复了C++11之前的双重检查锁定的问题,其中tmp
的存储可能在对象完全构造之前对其他线程可见。
换句话说,我没有查看实例,而是使用原子布尔值来确保DCLP工作,并且第二个tmp中的任何内容都必须同步并运行一次。这是正确的吗?
不,因为您将false
存储在此处:
// store back the tmp atomically
is_first.store(tmp, std::memory_order_release);
这意味着在对该函数的下一次调用中,您将创建另一个Singleton
并泄漏第一个。应该是:
is_first.store(true, std::memory_order_release);
如果你解决了这个问题,我认为这是正确的,但在典型的实现中,它使用了更多的内存(sizeof(atomic<bool>)+sizeof(Singleton*)
可能比sizeof(atomic<Singleton*>)
多(,并且通过将逻辑拆分为两个变量(一个布尔值和一个指针(,你会更容易出错,就像你所做的那样。因此,与原始指针相比,这样做没有任何好处,因为指针本身也充当布尔值,因为你可以直接查看指针,而不是某些可能设置不正确的布尔值。
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 为什么在保护模式下继承升级不起作用
- 如何在全屏模式下(在OpenGL中)使背景透明
- 为什么使用__LINE_的代码在发布模式下在MSVC下编译,而不是在调试模式下
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 此模式的C++RegEx
- avrogencpp能为模式中的每种类型生成单独的头文件吗
- 使用可变模板的Broadcaster/Listener模式
- c++方法参数只能在linux的发布模式下自行更改
- 资源管理设计模式
- 使用 mod_gsoap 部署服务时,如何在 Gsoap 中更改 soap 上下文的模式?
- C++ 无法在字符数组中使用 for 循环打印字母模式
- 小字符串优化(调试与发布模式)
- 可视化C++:发布模式的运行时库作为'Multi-threaded Debug DLL'
- 如何设计具有不同类型的通知和观察器的观察者模式?
- 在C++的一系列数字中查找重复模式
- 是否允许使用带有"w+"模式的 freopen 进行标准设置?
- C++ 使用存储在动态数组中的文本文件中的数据查找模式
- Boost upgrade_lock和DCLP(双重检查锁定模式)
- 这种在C++11中实现双重检查锁模式(DCLP)是否正确