既然C++11有了多线程,我想知道在不使用互斥的情况下实现延迟初始化的singleton的正确方法是什么(出于性能原因)。我想到了这个,但我不太擅长写无锁定代码,所以我在寻找一些更好的解决方案。
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
# include <atomic>
# include <thread>
# include <string>
# include <iostream>
using namespace std;
class Singleton
{
public:
Singleton()
{
}
static bool isInitialized()
{
return (flag==2);
}
static bool initizalize(const string& name_)
{
if (flag==2)
return false;// already initialized
if (flag==1)
return false;//somebody else is initializing
if (flag==0)
{
int exp=0;
int desr=1;
//bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr)
bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr);
if (! willInitialize)
{
//some other thread CASed before us
std::cout<<"somebody else CASed at aprox same time"<< endl;
return false;
}
else
{
initialize_impl(name_);
assert(flag==1);
flag=2;
return true;
}
}
}
static void clear()
{
name.clear();
flag=0;
}
private:
static void initialize_impl(const string& name_)
{
name=name_;
}
static atomic<int> flag;
static string name;
};
atomic<int> Singleton::flag=0;
string Singleton::name;
void myThreadFunction()
{
Singleton s;
bool initializedByMe =s.initizalize("1701");
if (initializedByMe)
s.clear();
}
int main()
{
while (true)
{
std::thread t1(myThreadFunction);
std::thread t2(myThreadFunction);
t1.join();
t2.join();
}
return 0;
}
请注意,clear()
只是用于测试,真正的singleton不会具有该功能。
C++11消除了手动锁定的需要。如果静态局部变量已经初始化,则应等待并发执行。
§6.7 [stmt.dcl] p4
如果在初始化变量时控件同时进入声明,则并发执行应等待初始化完成。
因此,simple有一个static
函数,如下所示:
static Singleton& get() {
static Singleton instance;
return instance;
}
这将在C++11中正常工作(当然,只要编译器正确地实现了标准的这一部分)。
当然,真正的正确答案是不要使用singleton,句点。
也许使用C++11实现单例的最简单方法是:
警告:尽管这是根据C++11标准工作的(静态初始化程序是线程安全的),但这在Microsoft Visual C++2012中没有正确实现(静态初始化器是线程不安全的)。如果您的目标是VC2012,那么您必须使用不同的方法,因为它没有完全实现C++11标准。
class Singleton {
public:
static Singleton& Instance() {
// Since it's a static variable, if the class has already been created,
// it won't be created again.
// And it **is** thread-safe in C++11.
static Singleton myInstance;
// Return a reference to our instance.
return myInstance;
}
// delete copy and move constructors and assign operators
Singleton(Singleton const&) = delete; // Copy construct
Singleton(Singleton&&) = delete; // Move construct
Singleton& operator=(Singleton const&) = delete; // Copy assign
Singleton& operator=(Singleton &&) = delete; // Move assign
// Any other public methods.
protected:
Singleton() {
// Constructor code goes here.
}
~Singleton() {
// Destructor code goes here.
}
// And any other protected methods.
}
IMHO,实现singleton的最佳方式是使用"双重检查,单锁"模式,您可以在C++11中移植实现该模式:双重检查锁定在C++11中得到修复这种模式在已经创建的情况下是快速的,只需要一个指针比较,在第一个用例中是安全的。
如前所述,C++11保证了静态局部变量的构造顺序安全。在C++11中,局部静态变量初始化线程安全吗?所以使用该模式是安全的。然而,Visual Studio 2013还不支持它:-(请参阅此页上的"magic statics"行,因此如果您正在使用VS2013,您仍然需要自己完成
不幸的是,没有什么是简单的。上面模式引用的示例代码不能从CRT初始化中调用,因为静态std::mutex有一个构造函数,因此如果所述调用是CRT初始化的副作用,则不能保证在第一次调用以获取singleton之前进行初始化。要绕过,您必须使用的不是互斥,而是指向互斥的指针,它保证在CRT初始化开始前为零初始化。然后,您必须使用std::atomic::compare_exchange_strong来创建和使用互斥。
我假设C++11线程安全的本地静态初始化语义即使在CRT初始化期间被调用时也能工作。
因此,如果您有C++11线程安全的本地静态初始化语义可用,请使用它们。如果没有,您还有一些工作要做,甚至更重要的是,如果您希望您的单例在CRT初始化期间是线程安全的。
template<class T>
class Resource
{
Resource<T>(const Resource<T>&) = delete;
Resource<T>& operator=(const Resource<T>&) = delete;
static unique_ptr<Resource<T>> m_ins;
static once_flag m_once;
Resource<T>() = default;
public :
virtual ~Resource<T>() = default;
static Resource<T>& getInstance() {
std::call_once(m_once, []() {
m_ins.reset(new Resource<T>);
});
return *m_ins.get();
}
};
很难理解您的方法,因为您没有按预期使用代码。。。也就是说,singleton的常见模式是调用instance()
来获取单个实例,然后使用它(此外,如果您真的想要singleton,那么任何构造函数都不应该是公共的)。
无论如何,我认为你的方法是不安全的,考虑到两个线程试图获取singleton,第一个更新标志的线程将是唯一一个初始化的线程,但initialize
函数将在第二个线程早期退出,该线程可能会在第一个线程完成初始化之前继续使用singleton。
initialize
的语义已损坏。如果你试图描述/文档函数的行为,你会有一些乐趣,最终会描述实现,而不是简单的操作。文档化通常是一种双重检查设计/算法的简单方法:如果你最终描述了如何而不是什么,那么你应该回到设计上来。特别是,不能保证在initialize
完成后,对象实际上已经初始化(仅当返回的值是true
时,有时如果是false
,但并不总是如此)。
#pragma once
#include <memory>
#include <mutex>
namespace utils
{
template<typename T>
class Singleton
{
private:
Singleton<T>(const Singleton<T>&) = delete;
Singleton<T>& operator = (const Singleton<T>&) = delete;
Singleton<T>() = default;
static std::unique_ptr<T> m_instance;
static std::once_flag m_once;
public:
virtual ~Singleton<T>() = default;
static T* getInstance()
{
std::call_once(m_once, []() {
m_instance.reset(new T);
});
return m_instance.get();
}
template<typename... Args>
static T* getInstance2nd(Args&& ...args)
{
std::call_once(m_once, [&]() {
m_instance.reset(new T(std::forward<Args>(args)...));
});
return m_instance.get();
}
};
template<typename T> std::unique_ptr<T> Singleton<T>::m_instance;
template<typename T> std::once_flag Singleton<T>::m_once;
}
此版本符合并发免费,其中不能保证100%支持c++11标准。它还提供了一种灵活的方式来实例化"拥有的"实例。即使c++11中的魔术static单词已经足够了,开发人员也可能需要对实例创建进行更多的控制。