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(); } } 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 = operator new(sizeof(Singleton)); new (_instance) Singleton; } } 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:
static Singleton* volatile _instance; };
volatile Singleton* volatile Singleton::_instance = 0; volatile Singleton* volatile Singleton::instance() { if (_instance == 0) { Lock lock; if (_instance == 0) { 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; if (tmp == 0) { Lock lock; tmp = pInstance; if (tmp == 0) { tmp = new Singleton; pInstance = tmp; } } return tmp; }
|
我,一个不同人,选择饿汉模式。
查了相关资料真的看了眼界,不过我估计不会写这样的代码
参考资料