4.解決多線程問題
上一篇實(shí)現(xiàn)的Singleton只能在單線程環(huán)境中使用,在多線程環(huán)境中會(huì)出現(xiàn)很多問題,看Instance()實(shí)現(xiàn)代碼:
1?static?Singleton&?Instance()?{
2?????if?(0?==?_instance)?{?//1
3?????????_instance?=?new?Singleton();?//2
4?????????atexit(Destroy);
5?????}
6?????return?*_instance;?//3
7?}
考慮如下情況:線程一調(diào)用Instance(),進(jìn)入//1,0 == _instance 返回true,線程一于是進(jìn)入//2。這時(shí)候線程一被掛起,線程二開始執(zhí)行,線程二調(diào)用Instance(),進(jìn)入//1,發(fā)現(xiàn)0 == _instance 仍然返回true,線程二于是也進(jìn)入//2,線程二繼續(xù)執(zhí)行到//3直到返回。這時(shí)候線程一被喚醒,繼續(xù)從//2開始執(zhí)行,這將會(huì)覆蓋線程二創(chuàng)建的_instance,線程一繼續(xù)執(zhí)行到//3直到返回...
解決方法很簡(jiǎn)單,引入相關(guān)同步對(duì)象(synchronization object)就行了,例如在win32平臺(tái)下可以如下實(shí)現(xiàn):
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*/
有了同步對(duì)象很容易就能夠?qū)懗鋈缦麓a:
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;
現(xiàn)在的Singleton雖然多線程安全,性能卻受到了影響。從Instance()中可以看到,實(shí)際上僅僅當(dāng)0 == _instance為true時(shí)才需要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?}
但是這樣還是會(huì)產(chǎn)生競(jìng)爭(zhēng)條件(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機(jī)制看起來像是一個(gè)完美的解決方案,但是在某些條件下仍然不行。簡(jiǎn)單的說,編譯器為了效率可能會(huì)重排指令的執(zhí)行順序(
compiler-based reorderings)。看這一行代碼:
_instance?=?new?Singleton();
在編譯器未優(yōu)化的情況下順序如下:
1.new operator分配適當(dāng)?shù)膬?nèi)存;
2.在分配的內(nèi)存上構(gòu)造Singleton對(duì)象;
3.內(nèi)存地址賦值給_instance。
但是當(dāng)編譯器優(yōu)化后執(zhí)行順序可能如下:
1.new operator分配適當(dāng)?shù)膬?nèi)存;
2.內(nèi)存地址賦值給_instance;
3.在分配的內(nèi)存上構(gòu)造Singleton對(duì)象。
當(dāng)編譯器優(yōu)化后,如果線程一執(zhí)行到2后被掛起。線程二開始執(zhí)行并發(fā)現(xiàn)0 == _instance為false,于是直接return,而這時(shí)Singleton對(duì)象可能還未構(gòu)造完成,后果...
上面說的還只是單處理器的情況,在多處理器(multiprocessors)的情況下,超線程技術(shù)必然會(huì)混合執(zhí)行指令,指令的執(zhí)行順序更無法保障。關(guān)于Double-Checked Locking的更詳細(xì)的文章,請(qǐng)看:
The "Double-Checked Locking is Broken" Declaration
5.使用volatile關(guān)鍵字
為了說明問題,請(qǐng)先考慮如下代碼:
?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()開啟了一個(gè)線程,該線程在while循環(huán)中檢測(cè)bool標(biāo)記_stopped,看是否該繼續(xù)執(zhí)行。如果想要結(jié)束這個(gè)線程,調(diào)用thread.stop()應(yīng)該沒問題。但是需要注意的是編譯器很有可能對(duì)_stopped的存取進(jìn)行優(yōu)化。如果編譯器發(fā)現(xiàn)_stopped被頻繁存取(_stopped在while循環(huán)中),編譯器可能會(huì)考慮將_stopped緩存到寄存器中,以后_stopped將會(huì)直接從寄存器存取。這時(shí)候如果某個(gè)線程調(diào)用了thread.stop(),對(duì)_stopped的修改將不會(huì)反映到寄存器中,thread將會(huì)永遠(yuǎn)循環(huán)下去...
為了防止編譯器優(yōu)化,用volatile關(guān)鍵字就OK了,volatile跟const的用法幾乎一樣,能用const的地方也都能用volatile。對(duì)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?T?*?volatile?Singleton<T>::_instance?=?0;
39?
40?#endif/*SINGLETON_H*/
測(cè)試代碼:
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?=?A::Instance();
15?????a.DoSomething();
16?
17?????return?0;
18?}
7.Singleton的析構(gòu)問題
到此Singleton已經(jīng)算比較完善了,但是依然算不上完美,因?yàn)榈浆F(xiàn)在只是解決了多線程問題,加入了模板支持,對(duì)于KDL problem(The Dead Reference Problem)依然沒法解決,可以說在實(shí)現(xiàn)Singleton模式時(shí),最大的問題就是多個(gè)有依賴關(guān)系的Singleton的析構(gòu)順序。雖然Modern C++ Design中給出了解決方案,但是Loki的實(shí)現(xiàn)太過復(fù)雜,在此就不詳細(xì)說明了,有興趣的可以看看Modern C++ Design,當(dāng)然了,Loki庫中用策略模式實(shí)現(xiàn)的Singleton也很不錯(cuò)!