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

非完美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也很不错!

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值