“呼 ~~~~ 啪!”
一個文件夾劃出一道優美的弧線,越過四張桌子,兩堵隔墻,一條走道,不偏不倚的穿過了正在交談的路人甲和路人乙,精準的命中了目標。放眼公司上下,擁有這般投擲手法的,只有 Solmyr ,而他的目標,自然是 zero 了。
“哎喲!”,zero 摸了摸被擊中的后腦勺,一半不甘一半認命的嘆了一口氣:不用問,他一定又有什么把柄被 Solmyr 抓住了。
“這次我又犯了什么錯誤了?”,zero 匆匆中斷了與方圓五十米內唯一的女程序員 pisces 之間愉快的閑聊,來到 Solmyr 身邊看看究竟哪里出了不妥。
“你剛剛提交的代碼會導致線程死鎖”,Solmyr 指著 zero 提交的一個函數:
void some_func()
{
pthread_mutex_lock(&mtx);
……
……
pthread_mutex_unlock(&mtx);
}
“會嗎?我明明在函數末尾釋放了互斥變量的呀?”
Solmyr 看了看 zero ,那表情分明在說:朽木不可雕也。他順手標出了函數中間的兩行代碼:
void some_func()
{
pthread_mutex_lock(&mtx);
……
if( status == E_FAIL )
return;
……
pthread_mutex_unlock(&mtx);
}
“Oops!”,zero 拍了一下腦門,“我知道了我知道了,我這就改。”
“你知道了?說說看你犯了什么錯誤?”
“我忘了在中間的函數返回點解鎖。”
“那你準備怎么解決這個問題”,很明顯,Solmyr 不打算就此輕輕放過 zero。
“嗯 …… 很簡單啊,在這里加上一行代碼,象這樣:”
if( status == E_FAIL )
{
pthread_mutex_unlock(mtx);
return;
}
Solmyr 搖搖頭:“你這是頭痛醫頭,腳痛醫腳。如果你這個函數里不只一個鎖,不只一個返回點,你打算怎么做?在每個返回點解開每個鎖么?”
“嗯 …… 你是指我應該遵循一個函數只有一個返回點的原則?”,zero 撓撓頭,有些不太確定。
“我不是指這個。有些情況下,硬要讓函數只有一個返回點會導致巨大的 if/else 結構,降低代碼的可讀性。而且,即使你的函數只有一個返回點,你還是有可能遇到這個問題。考慮這樣的函數:”,Solmyr 飛快的鍵入:
void some_func()
{
pthread_mutex_lock(&mtx);
……
// 中間沒有其他返回點
……
foo(); // 由其他程序員實現的函數
……
pthread_mutex_unlock(&mtx);
}
“看起來一點問題也沒有,可是如果 foo 這個函數丟出異常的話,會出現什么情況?”
“嗯
…… 如果我們函數里沒有捕獲這個異常的話 …… 它會導致 some_func 函數在調用 foo 的這一點中斷 …… 哎呀 ……”,zero
發現了問題所在。“那么只能在每個可能拋出異常的函數調用點用 try 捕獲所有異常,然后 ……”,zero 越說越小聲,“ …… 然后在
catch 里面解鎖,再重新拋出 ……” zero 停了下來,煩惱的撓著頭,發現他連自己都說服不了:這樣的解法實在是太繁瑣、太容易引入錯誤了。
“嗯?”
“好吧,我承認我不知道該怎么辦了,Solmyr ,這種情況應該怎么處理呢?”
“回憶一下,前兩天我們在飯桌上討論過什么?”(參見“Solmyr 的小品文系列”的前一期,“垃圾收集”)
“你是說垃圾收集嗎?哎 …… 可是 …… 那是處理內存泄漏的呀?和這個問題有什么關系?”
“我不是指具體的解法,”,Solmyr 搖搖頭,“關鍵是上次討論中引入的具有普遍性的原則,也就是 ……” Solmyr 停了下來,轉頭看著 zero 。
“…… ……”
“唉 ……”,Solmyr 用別人模仿不來的無奈表情 —— 按照他自己的說法,這是多年培訓工作的積累 —— 嘆了口氣:“我說 zero,你還很年輕,不會這么早就記憶力衰退了吧?”
…… 真是可惡的家伙,zero 心中恨恨的想。
Solmyr 的聲音再度在 zero 接近崩潰邊緣的時候響了起來:“如果你希望保證某些事情成對出現,請使用 ……”
“構造函數與析構函數!”,zero 生怕錯過了顯示自己并非“記憶力衰退”的機會。
“不用喊那么大聲。”,Solmyr 皺了皺眉,“你把前排觀眾都嚇壞了。”
“?!!!”,zero 迅速轉身,發現附近不知什么時候圍滿了公司的同事,每個人都“正常”在做自己的事情,只是動作稍顯忙亂而已 ……
解決了四周的“觀眾”之后,zero 回到了顯示器前,信心滿滿:“我知道了 Solmyr ,這里我們可以用和上次處理 分配/釋放 內存非常類似的手段來處理 加鎖/解鎖,只要寫一個非常簡單的類就行了,象這樣:”,zero 一邊說,一邊鍵入:
class auto_lock
{
public:
auto_lock(pthread_mutex_t mtx) : m_mtx(mtx)
{
pthread_mutex_lock(&m_mtx); // 構造時加鎖
}
~auto_lock()
{
pthread_mutex_unlock(&m_mtx); // 析構時解鎖
}
private:
pthread_mutex_t& m_mtx;
}
void some_func()
{
auto_lock(mtx);
……
// return 、foo ,隨便什么東西都行
……
// 結束的時候同樣不用解鎖
}
“這樣一來,我之前遇到的問題就全解決了,我可以自由的實現我的函數,不論什么時候返回或者遇到異常,我都可以肯定 mtx 將會被解鎖,不用擔心線程死鎖的問題。”
“嗯,
不錯。” Solmyr
贊許的點了點頭,開始總結:“實際上這是一個非常常用的手段,除了我們討論過的兩種情況而外,還可以應用在很多場合。比如網絡訪問中的建立連接和斷開連
接,數據庫訪問中的登錄與退出登錄,還可以方便的用它來實現測量一個函數平均運行耗時的測試工具,等等等等。不過萬變不離其宗,在這一切應用的背后是一個
統一的原則 ……”
Solmyr 頓了一頓,zero 心領神會的接了上去:
“如果你希望保證某些事情成對出現,請使用構造函數與析構函數。”