1. 程式人生 > >執行緒同步之利器(2)——區域鎖(Scoped locking)

執行緒同步之利器(2)——區域鎖(Scoped locking)

什麼是區域鎖

確切的說,區域鎖(Scoped locking)不是一種鎖的型別,而是一種鎖的使用模式(pattern)。這個名詞是Douglas C. Schmidt於1998年在其論文Scoped Locking提出,並在ACE框架裡面使用。但作為一種設計思想,這種鎖模式應該在更早之前就被業界廣泛使用了。

區域鎖實際上是RAII模式在鎖上面的具體應用。RAII(Resource Acquisition Is Initialization)翻譯成中文叫“資源獲取即初始化”,最早是由C++的發明者 Bjarne Stroustrup為解決C++中資源分配與銷燬問題而提出的。RAII的基本含義就是:C++中的資源(例如記憶體,檔案控制代碼等等)應該由物件來管理,資源在物件的建構函式中初始化,並在物件的解構函式中被釋放。STL

中的智慧指標就是RAII的一個具體應用。RAII在C++中使用如此廣泛,甚至可以說,不會RAII的裁縫不是一個好程式設計師。

問題提出

先看看下面這段程式,Cache是一個可能被多個執行緒訪問的快取類,update函式將字串value插入到快取中,如果插入失敗,則返回-1。

從這個程式中可以看出,為了保證程式不會死鎖,每次函式需要return時,都要需要呼叫unlock函式來釋放鎖。不僅如此,假設cache.insert(value)函式內部突然丟擲了異常,程式會自動退出,鎖仍然能不會釋放。實際上,不僅僅是return,程式中的goto, continue, break語句,以及未處理的異常,都需要程式設計師檢查鎖是否需要顯示釋放,這樣的程式是極易出錯的。

同樣的道理,不僅僅是鎖,C++中的資源釋放都面臨同樣的問題。例如前一陣我在閱讀wget原始碼的時候,就發現雖然一共只有2萬行C程式碼,但是至少有5處以上的return語句忘記釋放記憶體,因此造成了記憶體洩露。

區域鎖的實現

但是自從C++有了有可愛的RAII設計思想,資源釋放問題就簡單了很多。區域鎖就是把鎖封裝到一個物件裡面。鎖的初始化放到建構函式,鎖的釋放放到解構函式。這樣當鎖離開作用域時,解構函式會自動釋放鎖。即使執行時丟擲異常,由於解構函式仍然會自動執行,所以鎖仍然能自動釋放。一個典型的區域鎖

將策略鎖應用到前面的update函式如下

 

基本的區域鎖就這麼簡單。如果覺得這樣鎖的力度太大,可以用中括號來限定鎖的作用區域,這樣就能控制鎖的力度。如下

 

區域鎖的改進方案

上面設計的區域鎖一個缺點是靈活行,除非離開作用域,否則不能夠顯式釋放鎖。如果為一個區域鎖增加顯式釋放介面,一個最突出的問題是有可能會造成鎖的二次釋放,從而引發程式錯誤。

例如

為了避免二次釋放鎖引發的錯誤,區域鎖需要保證只能夠鎖釋放一次。一個改進的區域鎖如下:

 

可以看出,這種方案在加鎖失敗或者鎖的多次釋放情況下,不會引起程式的錯誤。

缺點:

區域鎖固然好使,但也有不可避免的一些缺點

(1) 對於非遞迴鎖,有可能因為重複加鎖而造成死鎖。

(2) 執行緒的強制終止或者退出,會造成區域鎖不會自動釋放。應該儘量避免這種情形,或者使用一些特殊的錯誤處理設計來確保鎖會釋放。

(3) 編譯器會產生警告說有變數只定義但沒有使用。有些編譯器選項甚至會讓有警告的程式無法編譯通過。在ACE中,為了避免這種情況,作者定義了一個巨集如下

#define UNUSED_ARG(arg) { if (&arg) /* null */; }

使用如下:

這樣編譯器就不會再警告了。

擴充套件閱讀:小技巧--如何在C++中實現Java的synchronized關鍵字

下篇文章中,我們將介紹一下雙檢測鎖(DCL)。