C++單例模式——並非你想的那像簡單


Author:WenHui,WuHan University,2012-12-10

前段時間忙着找工作,有一次面試官讓我用C++寫單例模式,我刷刷刷提筆就來。於是就隨手寫了如下的代碼v1版

 1 class Singlon
 2 { 
 3 private:
 4     static Singlon * inst;
 5 
 6 public:      
 7     static Singlon& instance()
 8     {
 9         if(inst != NULL) 
10         {
11             return *inst;
12         }
13         else 
14         {
15             inst = new Singlon();             
16             return *inst;
17         }
18     } // instance
19 }; // class Singlon
20 
21 Singlon* Singlon::inst = NULL;

 

v1是一份很標准的單例模式代碼,但不夠實用。面試官看着我,說“你再好好想想...”

我原本很自負,結果被問傻了,緊張之下依稀想起《程序員面試寶典C++版》的內容,好像沒考慮多線程問題。於是絞盡腦汁地回想C++中的線程互斥,還好記起點LINUX C下互斥鎖,於是乎就有了改進的v2版

 1 class Singlon
 2 { 
 3 private:
 4     static Singlon * inst;
 5     pthread_mutex_t inst_lock;
 6 
 7 public:
 8     static Singlon& instance()
 9     {
10         if(inst != NULL)
11         {
12             return *inst;
13         }
14         else 
15         {
16             pthread_mutex_lock(&inst_lock);
17             if(inst == NULL)
18             {
19                 inst = new Singlon();
20             } // if
21             pthread_mutex_unlock(&inst_lock);
22             return *inst;
23         } // else
24     } // instance
25 }; // class Singlon
26 
27 Singlon* Singlon::inst = NULL;
28 Singlon::inst_lock = PTHREAD_MUTEX_INITIALIZER;

 

v2中首先直接判斷inst是否為NULL,若不為NULL則避免調用互斥鎖,因為互斥鎖不僅使用系統調用,而且將導致多線程競爭等待,產生睡眠。

當寫完v2時,終於有種如釋重負的感覺,很欣慰,可是面試官冷冷地問我:“你仔細看看,還有什么問題么?”當時腦子一懵,TMD的還有問題?fuck!仔細再check了一下,發現if語句確實還可以有優化的余地,於是膽戰心驚地寫下了v3版:

 1 class Singlon
 2 { 
 3 private:
 4     static Singlon * inst;
 5     pthread_mutex_t inst_lock;
 6 
 7 public:
 8     static Singlon& instance()
 9     {
10         if(likely(inst != NULL)) 
11         {
12             return *inst;
13         }
14         else 
15         {
16             pthread_mutex_lock(&inst_lock);
17             if(likely(inst != NULL))
18             {
19                 pthread_mutex_unlock(&inst_lock);
20                 return *inst;
21             } // if
22             else 
23             {
24                 inst = new Singlon();
25                 pthread_mutex_unlock(&inst_lock);
26                 return *inst;
27             } // else
28         } // else
29     } // instance
30 }; // class Singlon
31 
32 Singlon* Singlon::inst = NULL;
33 Singlon::inst_lock = PTHREAD_MUTEX_INITIALIZER;

 

v3由於互拆鎖會導致效用問題,越盡早釋放越好,所以將一條if語句拆成兩條、遲早釋放,並且將條件概率大的情況放到if而非else中,用likely在匯編層上優化。我感覺這次大體上應該符合面試官要求了,但是面試官繼續追問,“我再給你一次機會好好想想……”當時正值午餐時間,早晨沒吃飯,肚子餓得咕咕叫,腦子都快瘋了。幸好最后換了個思路,寫下v4版:

 1 class Singlon
 2 { 
 3 private:
 4     static Singlon * inst;
 5 
 6 public:
 7     static Singlon& instance()
 8     {           
 9         return *inst;
10     } // instance
11 }; // class Singlon
12 
13 Singlon* Singlon::inst = new Singlon();

 

當提交給面試官看時,心想”今天真是倒霉”,幸而走屎運,v4預先創建好一個對象。雖然浪費空間,但不用再考慮TMD的線程互斥問題。面試官看了之后,語重心長地講“你這個浪費空間太大,我要你實現初次使用時再申請。給你最后一次機會檢查檢查代碼。”

暈!當時感覺已經能用的招都用了,后來放棄了,根他解釋了下v3版代碼,然后問他到底哪有問題。他說:“你為什么要使用互斥鎖?”於是給他解釋LINUX下互斥鎖的實現機制。MutexLock使用SpinLock實現互斥,循環檢測並休眠。當時突然想起可以直接用SpinLock實現v3的互斥,因為競爭僅發生一次,SpinLock直接采用循環檢測不休眠的方式,使用“內存屏障”、“CPU屏障”等技術保證內存和CPU指令的“有序”。v5版如下: 

 1 class Singlon
 2 { 
 3 private:
 4     static Singlon * inst;
 5     static raw_spinlock_t inst_lock;
 6 
 7 public:
 8     static Singlon& instance()
 9     {
10         if(likely(inst != NULL)) 
11         {
12             return *inst;
13         }
14         else 
15         {
16             spin_lock(&inst_lock);
17             if(likely(inst != NULL))
18             {
19                 spin_unlock(&inst_lock);
20                 return *inst;
21             } // if
22             else 
23             {
24                 inst = new Singlon();
25                 spin_unlock(&inst_lock);
26                 return *inst;
27             } // else
28         } // else
29     } // instance
30 }; // class Singlon
31 
32 Singlon* Singlon::inst = NULL;
33 spin_lock_init(Singlon::inst_lock);

 

面試官看完v5版之后,問spin_lock的實現原理,由於看過LINUX內核怎么實現所以說得比較清楚。聽完我的解釋,當時已經中午一點多,他也沒有再多說什么,終於結束這個該死的問題。其實C++沒有像JAVA和C#那樣從語法上提供同步互斥的機制,我中間也表示希望換成C#,但他拒絕了,因為自我簡介時說精通C++。唉,~~~~~~

其實,v5版本還可以優化,例如采用無鎖的辦法。但總得說來,只是將spin_lock的代碼簡化了一下內嵌進來。不知道是否有哪位高人可以指點一二,給一個最終優化的C++版單例模式?


《無鎖隊列的實現》,http://www.kuqin.com/algorithm/20120907/330193.html

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM