共享指针的双重检查锁定 免责声明:我来自java背景,因此,我不知道C++内部的许多内部(以及相关库)是如何工作的。
我已经读了足够多的书,知道双重检查锁定是邪恶的,而单例模式的正确和安全实现需要适当的工具 我相信下面的代码可能是不安全的,可能会受到编译器重新排序和未初始化对象赋值的影响,但我不确定我是否遗漏了一些我不了解的语言共享指针的双重检查锁定 免责声明:我来自java背景,因此,我不知道C++内部的许多内部(以及相关库)是如何工作的。,c++,multithreading,boost,double-checked-locking,C++,Multithreading,Boost,Double Checked Locking,我已经读了足够多的书,知道双重检查锁定是邪恶的,而单例模式的正确和安全实现需要适当的工具 我相信下面的代码可能是不安全的,可能会受到编译器重新排序和未初始化对象赋值的影响,但我不确定我是否遗漏了一些我不了解的语言 typedef boost::shared_ptr<A> APtr; APtr g_a; boost::mutex g_a_mutex; const APtr& A::instance() { if (!g_a) { boost::mutex::s
typedef boost::shared_ptr<A> APtr;
APtr g_a;
boost::mutex g_a_mutex;
const APtr& A::instance()
{
if (!g_a)
{
boost::mutex::scoped_lock lock(g_a_mutex);
if (!g_a)
{
g_a = boost::make_shared<A>();
}
}
return g_a;
}
typedef boost::shared_ptr APtr;
APtr g_a;
boost::mutex g_a_mutex;
const APtr&A::instance()
{
如果(!g_a)
{
mutex::作用域锁定锁(g_a_mutex);
如果(!g_a)
{
g_a=boost::make_shared();
}
}
返回g_a;
}
我相信实际的代码是在C++03下编译的
这个实现是否不安全?如果是这样,那么如何?< p>这在现代C++中是绝对不必要的。对于恐龙以外的任何人来说,单例代码都应该像这样简单:
A& A::instance() {
static A a;
return a;
}
在C++11及更高版本中100%线程安全
然而,我必须声明:单身是邪恶的反模式,应该永远禁止
论原始代码的线程安全
是的,只要代码不安全。由于读取是在互斥锁之外完成的,因此对
g_a
本身的修改完全可能是可见的,但对对象g_a
的修改并不指向。因此,线程将绕过互斥锁并返回指向非构造对象的指针。是的,双重检查锁定模式在古语言中是不安全的。我做的最好的解释是:
问题显然是行分配实例——编译器可以自由地分配对象,然后将指针分配给它,或者将指针设置到要分配的位置,然后分配它
如果您使用的是C++11
std::shared_ptr
,则可以在共享指针对象上使用,这保证了它们之间以及与互斥体的正确组合;但是如果你使用的是C++11,你也可以使用一个动态初始化的static
变量,它保证是线程安全的。IMHO,读写不带锁是不好的:当你开始读的时候,如果有人在g_a
里面写东西会附加什么?没有任何东西可以确保读取不会被写入g_a
的进程中断,并在之后继续。如果(!g_a)先删除,这将是安全的。我在别处看到过这个建议,但我不拥有有问题的代码,我只是在分析它,我相信实际的二进制文件是在C++03下编译的。我会弄清楚的。但是,我完全同意应该禁止单身人士。@afsantos,代码是不安全的。我已经解释了原因。“singleton是邪恶的反模式”规则的例外——如果返回的singleton对象是不可变的,那么就没关系了,因为如果该对象永远不可变,就不会有线程安全问题modified@JeremyFriesner首先,单例的反模式性与线程安全无关。即使在单线程应用程序中,它也是一种反模式。第二,当我们谈论单例线程安全时,我们通常指的是它的创建,而不是“底层”对象的线程安全。因此,不可变与单线程安全概念无关。@SergeyA另一种选择是让每个调用方构造并使用自己的单独(但100%相同)对象,这会使用额外的内存和CPU时间,在不可变对象的情况下没有明显的好处。关于对象创建的线程安全性,您的帖子说您的示例“在C++11及更高版本中是100%线程安全的”,所以这似乎不是问题,不仅仅是初始化代码。这可能是CPU缓存在玩把戏。这可能是编译器中的一个优化,它在执行顺序、分配到主内存等方面做了非常有趣的事情。