C++:单例模式

C++:单例模式

单例模式简单来说就是整个程序运行时只能创建出一个对象。

核心办法:构造函数私有化

单例模式还分为两种:饿汉模式和饱汉模式

饿汉模式:程序运行直接创建对象(线程安全)

饱汉模式:使用对象时,若不存在创建对象(线程不安全)

双检测锁:解决饱汉模式线程不安全问题

饱汉模式

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
class Singleton
{
public:
static Singleton * getInstance()
{
if(_pInstance == nullptr)
{
_pInstance = new Singleton();
}
return _pInstance;
}

static void free()
{
if(_pInstance)
{
delete _pInstance;
}
}

void print() const
{ cout << "Singleton::print()" << endl; }

private:
Singleton(){ cout << "Singleton()" << endl; }
~Singleton() { cout << "~Singleton()" << endl; }

static Singleton * _pInstance;
};

//静态变量必须类外静态初始化
Singleton * Singleton::_pInstance = nullptr;

饿汉模式

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
class Singleton
{
public:
static Singleton * getInstance()
{
return _pInstance;
}

static void free()
{
delete _pInstance;
}

void print() const
{ cout << "Singleton::print()" << endl; }

private:
Singleton(){ cout << "Singleton()" << endl; }
~Singleton() { cout << "~Singleton()" << endl;delete _pInstance}

static Singleton * _pInstance;
};

//静态变量必须类外静态初始化
Singleton * Singleton::_pInstance = new 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
30
31
32
33
34
35
36
37
38
39
40
class Singleton
{
public:
static Singleton * getInstance()
{
//加锁
while(lock.set_and_test());
if(_pInstance == nullptr)
{
_pInstance = new Singleton();
}
//解锁
lock.clear();
return _pInstance;
}

static void free()
{
if(_pInstance)
{
delete _pInstance;
}
}

void print() const
{ cout << "Singleton::print()" << endl; }

private:
Singleton(){ cout << "Singleton()" << endl; }
~Singleton() { cout << "~Singleton()" << endl; }

static Singleton * _pInstance;
//原子锁,使用互斥锁等都可以
static atomic_flag lock;
};

//静态变量必须类外静态初始化
Singleton * Singleton::_pInstance = nullptr;
//锁初始化
lock=ATOMIC_FLAG_INIT;

有锁就会有开销,即使是原子锁效率高了,但是处理器时间花费的也多了(自旋锁)。这就会想到其实只需要第一次创建对象时加锁就够了。

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
40
41
42
43
class Singleton
{
public:
static Singleton * getInstance()
{
//加锁
if(_pInstance == nullptr)
{
while(lock.set_and_test());
if(_pInstance == nullptr)
{
_pInstance = new Singleton();
}
}
//解锁
//lock.clear();
return _pInstance;
}

static void free()
{
if(_pInstance)
{
delete _pInstance;
}
}

void print() const
{ cout << "Singleton::print()" << endl; }

private:
Singleton(){ cout << "Singleton()" << endl; }
~Singleton() { cout << "~Singleton()" << endl; }

static Singleton * _pInstance;
//原子锁,使用互斥锁等都可以
static atomic_flag lock;
};

//静态变量必须类外静态初始化
Singleton * Singleton::_pInstance = nullptr;
//锁初始化
lock=ATOMIC_FLAG_INIT;

你以为这样就解决问题了,实际不然。_pInstance = new Singleton();经过编译器会分为三个步骤。

1
2
3
4
5
6
7
8
9
10
11
Singleton* Singleton::instance() {
if (_instance == 0) {
Lock lock;
if (_instance == 0) {
_instance = // Step 3
operator new(sizeof(Singleton)); // Step 1
new (_instance) Singleton; // Step 2
}
}
return _instance;
}

如下情况依旧会出错:

  • 线程A进入了instance函数,并且执行了step1和step3,然后挂起。这时的状态是:_instance不NULL,而_instance指向的内存去没有对象!
  • 线程B进入了instance函数,发现_instance不为null,就直接return _instance了。

然后可能会想到volatile关键词,代码如下:

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

class Singleton {
public:
static volatile Singleton* volatile instance();
...
private:
// one more volatile added
static Singleton* volatile _instance;
};

// from the implementation file
volatile Singleton* volatile Singleton::_instance = 0;
volatile Singleton* volatile Singleton::instance() {
if (_instance == 0) {
Lock lock;
if (_instance == 0) {
// one more volatile added


Singleton* volatile temp = new Singleton;
_instance = temp;
}
}
return _instance;
}

这就大功告成了么?如果实在单核单线程处理器上算是解决了,但是多线程环境依旧不能保证。

C++ and the Perils of Double-Checked Locking论文给出了终极解决方案,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Singleton* Singleton::instance () {
Singleton* tmp = pInstance;
// insert memory barrier
if (tmp == 0) {
Lock lock;
tmp = pInstance;
if (tmp == 0) {
tmp = new Singleton;
// insert memory barrier
pInstance = tmp;
}
}
return tmp;
}

我,一个不同人,选择饿汉模式。

查了相关资料真的看了眼界,不过我估计不会写这样的代码

参考资料