单线程版本
template<typename T>
class Singleton {
public:
T &getInstance() {
if (value_ == nullptr) {//判断语句
vaule_ = new T();
}
return *value_;
}
private:
Singleton();
~Singleton();
static T *value_;
};
这个版本只适合单线程使用, 因为如果在多线程环境下可能两个线程同时执行到上面的判断语句, 进而同时进入if
为真的分支,最后两个线程产生了各自的实例, 这就破坏了单例的定义。
改进:多线程版本
template<typename T>
class Singleton {
public:
T &getInstance() {
MutexLock lock(mutex_); //RAII
if (value_ == nullptr) {
value_ = new T();
}
return *value_;
}
private:
Singleton();
~Singleton();
Mutex mutex_;
static T *value_;
}
既然在判断语句这里存在竞争, 那就直接加锁好了, 这样的处理方法总是正确的, 但是效率确实堪忧的, 因为所有的线程都要争这一个锁, 即使是单例实例已经构造出来了。注意在上面的写法中为了简化代码, 我用了RAII的写法, Mutex以及MutexLock的定义没有给出, 但是应该不难得到他们简单的定义。
改进:更好的效率
上个一个小节中写的单例模式之所以低效, 是因为即使单例已经被构造出来, 每个线程还是要争用锁, 根据这样的观察, 我们有以下改进:
template<typename T>
class Singleton {
public:
T &getInstance() {
if (value_ == nullptr) {
MutexLock lock(mutex_);
if (value_ == nullptr) {
value_ = new T();//#1
}
}
return *value_;
}
private:
Singleton();
~Singleton();
static T *value_;
Mutex mutex_;
}
上面的这种写法在C++11之前是不能保证正确的, 原因是这样的:上面标号为1的代码在底层执行的时候相当于下面的代码:
T* p = static_cast<T*>(operator new(sizeof(T)));
new (p) T();//#2
value_ = p; //#3
先为指针申请内存是确定的, 但是标准却没有指定2和3 的执行顺序, 那如果先执行2,再执行3是没有问题的, 但是如果反过来, 先执行3, 那其他线程判断value_为非空, 但其实现在的value_ 并没有赋有效值,从而产生错误。要想改正这个错误, 就需要把value_ = new T()
手动拆分成上面的三段式写法, 并且在2和3之间加入内存屏障, 强制保证2在3之前执行。
至此, 我们就得到了一个适合多线程版本的, 而且效率也不错的单例模式,锁在牺牲一定效率的前提先保证了多线程单例模式的正确性, 但是人们总是在探索正确而更加高效的写法, 比如, 如何在不用锁的情况下写出一个单例模式:
Meyers Singleton
template<typename T>
class Singleton {
public:
T &getInstance() {
static T value;
return value;
}
private:
Singleton();
~Singleton();
}
在C++11之后这种写法是正确的, 而且适用于多线程版本。但是在C++11之前, 这种写法不适用于多线程版本, 原因在于C++11之前并没有规定local static variable的内存模型, 好多编译器的实现是one check, 就拿上面的例子来说, 在旧版本的编译器上实现可能是这样的:
bool initialized = false;
char value[sizeof(T)];
T& getInstance()
{
if (!initialized)
{
initialized = true;
new (value) T();
}
return *(reinterpret_cast<T*>(value));
}
这种窘境我们在前面已经看到过了, 两个线程可能同时检查initialized
的值, 并且同时进入if分支, 造成两个线程各自为政的局面, 所谓单例中的“单”也就不复存在了。但是在C++11之后, 标准规定:某个线程的局部静态变量必须被初始化之后才能被其他线程访问, 所以one check的实现方式就一去不复返了, 自然能保证单例的正确。
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
最安心的方法: pthread_once
template<typename T>
class Singleton : Nocopyable
{
public:
static T& getInstance()
{
threads::pthread_once(&once_control_, init);
return *value_;
}
private:
static void init()
{
value_ = new T();
}
Singleton();
~Singleton();
static pthread_once_t once_control_;
static T* value_;
};
template<typename T>
pthread_once_t Singleton<T>::once_control_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;
posix规范里面的pthread_once能够保证注册的函数只运行一次, 不管C++的版本如何, 只要系统支持pthread, 这种方法就是最直接的。
附录1:
Mutex以及MutexLock定义:
class Mutex {
public:
Mutex() {
int err = 0;
err = pthread_mutex_init(&mutex_, nullptr);
if (err < 0) {
//error checking
}
}
void lock() {
pthread_mutex_lock(&mutex_);
}
void unlock() {
pthread_mutex_unlock(&mutex_);
}
~Mutex() {
pthread_mutex_destroy(&mutex_);
}
pthread_mutex_t *getInternalMutex() {
return &mutex_;
}
private:
pthread_mutex_t mutex_;
};
class MutexLock {
public:
MutexLock(Mutex &mutex) :mutex_(mutex) {
mutex_.lock();
}
~MutexLock() {
mutex_.unlock();
}
private:
Mutex &mutex_;
};