C++ 这是双重检查锁定的有效替代方案吗?
我想在多线程程序中使用单例模式。双重检查锁定方法似乎适合其效率,但这种方法是坏的,不容易得到正确的 我编写了以下代码,希望它可以作为双重检查锁定的替代方案。它是线程安全的单例模式的正确实现吗C++ 这是双重检查锁定的有效替代方案吗?,c++,multithreading,thread-safety,C++,Multithreading,Thread Safety,我想在多线程程序中使用单例模式。双重检查锁定方法似乎适合其效率,但这种方法是坏的,不容易得到正确的 我编写了以下代码,希望它可以作为双重检查锁定的替代方案。它是线程安全的单例模式的正确实现吗 static bool created = false; static Instance *instance = 0; Instance *GetInstance() { if (!created) { Lock lock; // acquire a lock, parame
static bool created = false;
static Instance *instance = 0;
Instance *GetInstance() {
if (!created) {
Lock lock; // acquire a lock, parameters are omitted for simplicity
if (!instance) {
instance = new Instance;
} else {
created = true;
}
}
return instance;
}
第一个调用将创建一个实例。第二个调用将设置为true。最后,所有其他调用将返回一个初始化良好的实例
它与双重检查锁定具有相同的可靠性。 你可以通过“三重检查”,甚至“四重检查”得到更多,但完全的可靠性是不可能的 请注意,声明局部静态变量将使编译器实现自己的逻辑
#include<memory>
#include "Instance.h" //or whatever...
Instance* GetInstance()
{
static std::unique_ptr<Instance> p(new Instance);
return p.get();
}
#包括
#包括“Instance.h”//或其他内容。。。
实例*GetInstance()
{
静态std::unique_ptr p(新实例);
返回p.get();
}
如果编译器配置为多线程环境,那么它应该使用互斥来保护静态p,并在第一次调用初始化p时管理锁。它还应该将p销毁链接到“at_exit”链的尾部,以便在程序结束时执行适当的销毁
[编辑]
由于这是C++11的要求,并且仅在某些C++03预标准中实现,请检查编译器实现和设置
现在,我只能确保Mingw4.6已经开启,VS2010已经做到了。它具有与双重检查锁定相同的可靠性。 你可以通过“三重检查”,甚至“四重检查”得到更多,但完全的可靠性是不可能的 请注意,声明局部静态变量将使编译器实现自己的逻辑
#include<memory>
#include "Instance.h" //or whatever...
Instance* GetInstance()
{
static std::unique_ptr<Instance> p(new Instance);
return p.get();
}
#包括
#包括“Instance.h”//或其他内容。。。
实例*GetInstance()
{
静态std::unique_ptr p(新实例);
返回p.get();
}
如果编译器配置为多线程环境,那么它应该使用互斥来保护静态p,并在第一次调用初始化p时管理锁。它还应该将p销毁链接到“at_exit”链的尾部,以便在程序结束时执行适当的销毁
[编辑]
由于这是C++11的要求,并且仅在某些C++03预标准中实现,请检查编译器实现和设置
现在,我只能确保Mingw4.6已在和VS2010上运行。不,这没有帮助。如果对
创建的和实例的写入是非原子的,则不能保证这些值对不锁定互斥锁的线程可见
e、 g.线程1调用getInstance
created
为false
,而instance
为null,因此它会锁定互斥锁并创建一个新实例。线程1再次调用getInstance
,这次将created
设置为true
。线程2现在调用getInstance
。通过处理器内存管理的变幻莫测,它将创建的
视为真
,但不能保证它也将实例
视为非空,即使如此,也不能保证指向的实例的内存值是一致的
如果您没有使用原子,那么您需要使用互斥体,并且必须将它们用于对受保护变量的所有访问
附加信息:如果您使用互斥锁,那么编译器和运行时将协同工作,以确保当一个线程释放互斥锁,而另一个线程获取同一互斥锁时,第二个线程可以看到第一个线程完成的所有写操作。这对于非原子访问不适用,对于原子访问也可能不适用,具体取决于编译器和运行时为您提供的内存排序约束(对于C++11原子,您可以选择排序约束)。不,这没有帮助。如果对创建的和实例的写入是非原子的,则不能保证这些值对不锁定互斥锁的线程可见
e、 g.线程1调用getInstance
created
为false
,而instance
为null,因此它会锁定互斥锁并创建一个新实例。线程1再次调用getInstance
,这次将created
设置为true
。线程2现在调用getInstance
。通过处理器内存管理的变幻莫测,它将创建的
视为真
,但不能保证它也将实例
视为非空,即使如此,也不能保证指向的实例的内存值是一致的
如果您没有使用原子,那么您需要使用互斥体,并且必须将它们用于对受保护变量的所有访问
附加信息:如果您使用互斥锁,那么编译器和运行时将协同工作,以确保当一个线程释放互斥锁,而另一个线程获取同一互斥锁时,第二个线程可以看到第一个线程完成的所有写操作。这对于非原子访问不适用,对于原子访问可能适用,也可能不适用,这取决于编译器和运行时为您提供的内存排序约束(对于C++11原子,您可以选择排序约束)。此代码包含竞态条件,在这种情况下,可以在另一个线程同时写入创建的时读取它
因此,它具有未定义的行为,并且不是编写代码的有效方法
正如肯尼特在评论中指出的那样,更好的选择是:
Instance* GetInstance() { static Instance instance; return &instance; }
此代码包含一个争用条件,即创建的可以在另一个线程同时写入时读取
因此,我
static Instance *instance = 0;
Instance *GetInstance() {
if (instance == NULL) //first check.
{
Lock lock; //scope lock.
if (instance == NULL) //second check, the second check must under the lock.
instance = new Instance;
}
return instance;
}