0%

单例模式学习

单例模式

单例模式的精髓是一个类只产生一个实例对象,当对象被构造以后,阻止构造、拷贝构造、赋值和析构操作。

单例模式可以考虑以下问题,如线程安全性和如何优化等。本文讨论学习单例模式过程中通过博客和书籍学习到的解决方案。

单线程(饿汉式)

保证类仅有一个实例,并提供一个访问它的全局访问点。

  • 构造函数、拷贝构造和赋值构造私有化,避免外界使用new来创造实例 (思考=delete和设为private的区别在哪里?)

  • 通过判断一个局部static变量来确定是否创造实例,static变量需要初始化

  • 通过接口GetInstance()来取得实例

由于这种设计方案使用了静态成员,类产生时就创建好了实例对象,也被称作饿汉模式,是一种

空间换时间的做法。同时饿汉式也是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton {
private:
Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);

public:
static Singleton& getInstance()
{
static Singleton obj_;
return obj;
}

~Singleton();
};

C++11中局部静态对象保证了线程线程安全性。使用方法:Singleton &instance_ = Singleton::getInstance();

多线程(懒汉模式)

  • 通过对临界区加锁,避免多个线程同时访问Singleton

  • 避免锁的开销,只有未被实例化时才加锁(双重检查锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Singleton {
private:
static Singleton* p_instance; //单例的对象
static std::mutex m_mutex;
...

public:
static Singleton& getInstance()
{
if(p_instance == nullptr)
{
std::unique_lock<std::mutex> ulk(mutex_)
if(p_instance == nullptr)
{
pInstance_ = new Singleton();
}
}
return *pInstance;
}

static void deleteInstance()
{
delete p_instantce;
p_instance = nullptr;
}
};

Singleton *Singleton::pInstance_ = nullptr;
std::mutex Singleton::mutex_;

但是双重检查锁存在cpu对指令重排,乱序造成的风险。学习C++的内存模型的过程参考博客https://www.cnblogs.com/haippy/p/3412858.html,得知new的过程中,cpu顺序执行是先调用malloc分配内存空间,再调用构造函数实例化对象,然后将p_instance指向刚分配的内存地址。在这个过程中,如果指令重排,在初始化之前让p_instance指向刚分配的内存,这样另外一个线程会造成错误的判断。

muduo做法

陈硕在书中谈到这个问题,并使用pthread_once来实现,Linux的threads库保证它指定的函数只执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
template<typename T>
class Singleton /*: boost::noncopyable 如果使用boost库就避免写拷贝和赋值构造函数*/
{
public:
static T& getInstance()
{
pthread_once(&ponce, &Singleton::init);
return *pInstance_;
}

static void destory()
{
delete pInstance_;
pInstance_ = NULL;
}

private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton<T>& operator=(const Singleton&);

static void init()
{
pInstance_ = new T();
}

private:
static pthread_once_t ponce_;
static T* pInstance_;
};


//静态变量与对象无关,初始化
template<typename T>
pthread_once_t Singleton<T>::ponce = PTHREAD_ONCE_INIT;

template<typenmame T>
T* Singleton<T>::pInstance = NULL;