非完美C++ Singleton实现[2]

原创 2007年09月13日 08:19:00

非完美C++ Singleton实现[2]

4.解决多线程问题
上一篇实现的Singleton只能在单线程环境中使用,在多线程环境中会出现很多问题,看Instance()实现代码:
1 static Singleton& Instance() {
2     if (0 == _instance) { //1

3         _instance = new Singleton(); //2
4         atexit(Destroy);
5 
    }
6     return *_instance; //3

7 }
考虑如下情况:线程一调用Instance(),进入//1,0 == _instance 返回true,线程一于是进入//2。这时候线程一被挂起,线程二开始执行,线程二调用Instance(),进入//1,发现0 == _instance 仍然返回true,线程二于是也进入//2,线程二继续执行到//3直到返回。这时候线程一被唤醒,继续从//2开始执行,这将会覆盖线程二创建的_instance,线程一继续执行到//3直到返回...

解决方法很简单,引入相关同步对象(synchronization object)就行了,例如在win32平台下可以如下实现:
synobj.h
 1 #ifndef SYNOBJ_H
 2 
#define SYNOBJ_H
 3 

 4 #include <windows.h>
 5 
 6 #define CLASS_UNCOPYABLE(classname) /
 7     private
: /
 8     classname##(const classname##&
); /
 9     classname##& operator=(const classname##&
);
10 

11 class Mutex {
12 
    CLASS_UNCOPYABLE(Mutex)
13 public
:
14     Mutex() :_cs() { InitializeCriticalSection(&
_cs); }
15     ~Mutex() { DeleteCriticalSection(&
_cs); }
16     void lock() { EnterCriticalSection(&
_cs); }
17     void unlock() { LeaveCriticalSection(&
_cs); }
18 private
:
19 
    CRITICAL_SECTION _cs;
20 
};
21 

22 class Lock {
23 
    CLASS_UNCOPYABLE(Lock)
24 public
:
25     explicit Lock(Mutex&
 cs) :_cs(cs) { _cs.lock(); }
26     ~
Lock() { _cs.unlock(); }
27 private
:
28     Mutex&
 _cs;
29 
};
30 

31 #endif/*SYNOBJ_H*/

有了同步对象很容易就能够写出如下代码:
singleton.h
 1 #ifndef SINGLETON_H
 2 
#define SINGLETON_H
 3 

 4 #include "synobj.h"
 5 
 6 class Singleton {
 7 public
:
 8     static Singleton& Instance() { // Unique point of access

 9         Lock lock(_mutex);
10         if (0 ==
 _instance) {
11             _instance = new
 Singleton();
12             atexit(Destroy); // Register Destroy function

13         }
14         return *
_instance;
15 
    }
16     void
 DoSomething(){}
17 private
:
18     static void Destroy() { // Destroy the only instance

19         if ( _instance != 0 ) {
20 
            delete _instance;
21             _instance = 0
;
22 
        }
23 
    }
24     Singleton(){} // Prevent clients from creating a new Singleton

25     ~Singleton(){} // Prevent clients from deleting a Singleton
26     Singleton(const Singleton&); // Prevent clients from copying a Singleton
27     Singleton& operator=(const Singleton&);
28 private
:
29     static
 Mutex _mutex;
30     static Singleton *_instance; // The one and only instance

31 };
32 

33 #endif/*SINGLETON_H*/

singleton.cpp
1 #include "singleton.h"
2 
3 Mutex Singleton::_mutex;
4 Singleton* Singleton::_instance = 0;
现在的Singleton虽然多线程安全,性能却受到了影响。从Instance()中可以看到,实际上仅仅当0 == _instance为true时才需要Lock。你很容易就写出如下代码:
1 static Singleton& Instance() {
2     if (0 ==
 _instance) {
3 
        Lock lock(_mutex);
4         _instance = new
 Singleton();
5 
        atexit(Destroy);
6 
    }
7     return *
_instance;
8 }
但是这样还是会产生竞争条件(race condition),一种广为人知的做法是使用所谓的Double-Checked Locking:
 1 static Singleton& Instance() {
 2     if (0 ==
 _instance) {
 3 
        Lock lock(_mutex);
 4         if (0 ==
 _instance) {
 5             _instance = new
 Singleton();
 6 
            atexit(Destroy);
 7 
        }
 8 
    }
 9     return *
_instance;
10 }
Double-Checked Locking机制看起来像是一个完美的解决方案,但是在某些条件下仍然不行。简单的说,编译器为了效率可能会重排指令的执行顺序(compiler-based reorderings)。看这一行代码:

_instance = new Singleton();

在编译器未优化的情况下顺序如下:
1.new operator分配适当的内存;
2.在分配的内存上构造Singleton对象;
3.内存地址赋值给_instance。


但是当编译器优化后执行顺序可能如下:
1.new operator分配适当的内存;
2.内存地址赋值给_instance;
3.在分配的内存上构造Singleton对象。


当编译器优化后,如果线程一执行到2后被挂起。线程二开始执行并发现0 == _instance为false,于是直接return,而这时Singleton对象可能还未构造完成,后果...

上面说的还只是单处理器的情况,在多处理器(multiprocessors)的情况下,超线程技术必然会混合执行指令,指令的执行顺序更无法保障。关于Double-Checked Locking的更详细的文章,请看:
The "Double-Checked Locking is Broken" Declaration

5.使用volatile关键字
为了说明问题,请先考虑如下代码:
 1 class MyThread : public Thread {
 2 public
:
 3     virtual void
 run() {
 4         while (!
_stopped) {
 5             //do something

 6         }
 7 
    }
 8     void
 stop() {
 9         _stopped = true
;
10 
    }
11 private
:
12 
    bool _stopped;
13 
};
14 

15 ...
16 
17 MyThread thread;
18 thread.start();
上面用thread.start()开启了一个线程,该线程在while循环中检测bool标记_stopped,看是否该继续执行。如果想要结束这个线程,调用thread.stop()应该没问题。但是需要注意的是编译器很有可能对_stopped的存取进行优化。如果编译器发现_stopped被频繁存取(_stopped在while循环中),编译器可能会考虑将_stopped缓存到寄存器中,以后_stopped将会直接从寄存器存取。这时候如果某个线程调用了thread.stop(),对_stopped的修改将不会反映到寄存器中,thread将会永远循环下去...

为了防止编译器优化,用volatile关键字就OK了,volatile跟const的用法几乎一样,能用const的地方也都能用volatile。对Singleton来说,修改如下两处即可:
1 //singleton.h中
2 static Singleton *_instance;
3 //改为

4 static Singleton * volatile _instance;
5 

6 //singleton.cpp中
7 Singleton* Singleton::_instance = 0;
8 //改为

9 Singleton* volatile Singleton::_instance = 0;


6.将Singleton泛化为模板
singleton.h
 1 #ifndef SINGLETON_H
 2 
#define SINGLETON_H
 3 

 4 #include "synobj.h"
 5 
 6 template<class T>
 7 class Singleton {
 8 
    CLASS_UNCOPYABLE(Singleton)
 9 public
:
10     static T& Instance() { // Unique point of access

11         if (0 == _instance) {
12 
            Lock lock(_mutex);
13             if (0 ==
 _instance) {
14                 _instance = new
 T();
15 
                atexit(Destroy);
16 
            }
17 
        }
18         return *
_instance;
19 
    }
20 protected
:
21 
    Singleton(){}
22     ~
Singleton(){}
23 private
:
24     static void Destroy() { // Destroy the only instance

25         if ( _instance != 0 ) {
26 
            delete _instance;
27             _instance = 0
;
28 
        }
29 
    }
30     static
 Mutex _mutex;
31     static T * volatile _instance; // The one and only instance

32 };
33 

34 template<class T>
35 Mutex Singleton<T>::_mutex;
36 

37 template<class T>
38 * volatile Singleton<T>::_instance = 0;
39 

40 #endif/*SINGLETON_H*/

测试代码:
test.cpp
 1 #include "singleton.h"
 2 
 3 class A : public Singleton<A> {
 4     friend class Singleton<A>
;
 5 protected
:
 6 
    A(){}
 7     ~
A(){}
 8 public
:
 9     void
 DoSomething(){}
10 
};
11 

12 int main() {
13 

14     A &= A::Instance();
15 
    a.DoSomething();
16 

17     return 0;
18 }


7.Singleton的析构问题
到此Singleton已经算比较完善了,但是依然算不上完美,因为到现在只是解决了多线程问题,加入了模板支持,对于KDL problem(The Dead Reference Problem)依然没法解决,可以说在实现Singleton模式时,最大的问题就是多个有依赖关系的Singleton的析构顺序。虽然Modern C++ Design中给出了解决方案,但是Loki的实现太过复杂,在此就不详细说明了,有兴趣的可以看看Modern C++ Design,当然了,Loki库中用策略模式实现的Singleton也很不错!

 

C++完美实现Singleton模式

  • 2014年05月21日 13:04
  • 16KB
  • 下载

C++完美实现Singleton模式

  • 2016年08月01日 10:40
  • 102KB
  • 下载

C++完美实现Singleton模式

Singleton模式是常用的设计模式之一,但是要实现一个真正实用的设计模式却也不是件容易的事情。 1.         标准的实现 class Singleton { public:        ...

C++完美实现Singleton模式

Singleton模式是常用的设计模式之一,但是要实现一个真正实用的设计模式却也不是件容易的事情。 1.         标准的实现 class Singleton { public:   ...

C++完美实现Singleton模式

Singleton模式是常用的设计模式之一,但是要实现一个真正实用的设计模式却也不是件容易的事情。 1. 标准的实现 class Singleton { public: ...
  • camel20
  • camel20
  • 2012年09月19日 19:35
  • 1363

C++完美实现Singleton模式

转载于:http://www.cnblogs.com/08shiyan/archive/2012/03/16/2399617.html Singleton模式是常用的设计模式之一,但是要实现...

C++ 非完美Singleton

非完美C++ Singleton实现[1] Singleton模式是一种非常简单的设计模式,这种模式很常用也很容易被滥用。当你设计应用程序的时候,经常会遇到某些对象在整个程序的生命周期应该仅有一...
  • wpc320
  • wpc320
  • 2012年07月25日 15:53
  • 869

剑指offer 面试题2 Singleton模式 C++实现

参考: 1、题目来源《剑指offer 名企面试官精讲典型编程题 纪念版-何海涛 著》 2、C++设计模式——单例模式: http://www.jellythink.com/arch...

C++ 实现的singleton 模式

  • 2010年10月08日 16:13
  • 615B
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:非完美C++ Singleton实现[2]
举报原因:
原因补充:

(最多只允许输入30个字)