欲加速,故生并发之策,存临界不可达,故而生
锁,阻塞患于
锁,固又生避阻之策。。。
此刻主要想记录下关于并发中
锁相关的内容,当然,我也只想记录一些真正有效用的东西。
可以说
锁是衍生自并发,故谈并发必然至
锁。
此篇不欲从减少临界区以提高并发度的角度来考虑,不会深度探索诸如如何基于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的
锁对豍