欲加速,故生并發之策,存臨界不可達,故而生
鎖,阻塞患于
鎖,固又生避阻之策。。。
此刻主要想記錄下關于并發中
鎖相關的內容,當然,我也只想記錄一些真正有效用的東西。
可以說
鎖是衍生自并發,故談并發必然至
鎖。
此篇不欲從減少臨界區以提高并發度的角度來考慮,不會深度探索諸如如何基于CAS來構建高質量無
鎖數據結構等。
而是從反方向,從如何很好的運用
鎖策略的角度來考慮。
這里要強調的一點是,無論如何,臨界區
在一個系統中基本上是必然存
在的。CAS的確可以
在很多地方通過自身機制來消去
鎖以達到化解臨界區之目的。但是這個實現復雜度卻又著實提升了,況且,也不是所有地方都能夠或者值得去CAS。相比而言,
鎖比較容易理解。
好了,切入主題:并發之------
鎖策略!
好,首先是定界加
鎖策略。
定界加鎖(Scoped Locking):能確保當控制進入到某一范圍時,自動獲得
鎖,而當控制離開范圍時,自動釋放
鎖,不管從該范圍返回的路徑是什么。
給段普通的
C++代碼sample:
bool increment(const string &path) {
Item *item = lookup_or_create(path);
lock.acquire();
if(entry==0){
lock.release();
return false;
} else {
entry->increment_hit_count();
lock.release();
return true;
}
}
這段代碼完全可以正常工作,但是lock.acquire()和lock.release()之間存
在著多路返回,如果以上情況復雜點,寫代碼的人不一定記得
在每個返回點都釋放
鎖。OK, 就算情況不復雜,拿上面的例子來看,如果entry->increment_hit_count()拋出了個異常,那如何???很顯然,
鎖得不到釋放了,這顯然會導致其它想要得到
鎖的線程永遠阻塞。
那怎么樣避免呢?定界加
鎖模式是正用于此。
你可以定義個哨兵類(guard)類,當控制進入一個區域時,哨兵類的
構造函數自動獲得一個
鎖,當控制離開這個區域時,哨兵類的析構函數自動釋放該
鎖,因為根據
C++的語義,即便
在程序中拋出一個異常,析構函數還是會執行。將哨兵類實例化,以
在定義臨界區的方法和塊區域中獲得或釋放
鎖。類似于autoPoint的手法,需要注意的是:一,
在哨兵類中要使用指向
鎖的指針而不是使用棧上
鎖對象,以防止對
鎖的復制或賦值;二,給哨兵類增加一個owner標志,用來表示哨兵是否成功獲得了
鎖,該標志也可以指示當錯誤地使用靜態/全局
鎖時,由“初始化錯誤”而導致的失敗。通過
在析構函數中檢查這個標志,可以避免當哨兵釋放它并不擁有的
鎖而產生的運行時錯誤。
哨兵類代碼示范:
class Thread_Mutex_Gurad {
public:
Thread_Mutex_Guard(Thread_Mutex &lock):lock_(&lock), owner_ (false){
lock_->acquire();
owner_ = true;
}
~Thread_Mutex_Guard(){
if(owner_) lock_->release();
}
private:
Thread_Mutex *lock_;
bool owner_;
Thread_Mutex_Guard(const Thread_Mutex_Guard &);
void operator=(const Thread_Mutex_Guard &);
};
以上模式是基于
C++的特性來玩的。
C++棧對象離開作用域時,必然調用其析構,這是死規則,所以這樣OK。
再拿java作下對比,如果使用synchronized關鍵字,OK,你不用理會上面的麻煩,JVM幫你搞定。但如果你用的是
ReentrantLock或者ReadWriteLock之類,那么,下面的寫法基本上就是死規矩了。
lock.lock();
try{
} finally{
lock.unlock();
}
以上代碼不能顯示獲取和釋放
鎖,你可以很簡單的給它加上。
現
在懶得寫東西了,下文后續補上。。。。。。
java下也有個finalize方法來做實例的資源銷毀,但是JVM作GC的時機不確定,并非對象離開作用域就立馬調用。所以你不要拿java的
鎖對豍