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