慎用读写锁
之所以要“慎用”,是因为能够利用读写锁真正达到想要的效果的情景不多,如果情景使用不正确,使用的效率还不如一般的锁。而且使用读写锁有一个非常容易的错误,在读锁递归使用时(重入时)如果有写锁lock,很多的实现版本上会发生死锁。下面详细说明。
Be wary of readers/writer locks. If there is a novice error when trying to break up a lock, it is this: seeing that a data structure is frequently accessed for reads and infrequently accessed for writes, one may be tempted to replace a mutex guarding the structure with a readers/writer lock to allow for concurrent readers. This seems reasonable, but unless the hold time for the lock is long, this solution will scale no better (and indeed, may scale worse) than having a single lock. Why? Because the state associated with the readers/writer lock must itself be updated atomically, and in the absence of a more sophisticated (and less space-efficient) synchronization primitive, a readers/writer lock will use a single word of memory to store the number of readers. Because the number of readers must be updated atomically, acquiring the lock as a reader requires the same bus transaction—a read-to-own—as acquiring a mutex, and contention on that line can hurt every bit as much.
直接翻译过来:小心读写锁。一个非常容易犯的初级错误就是当看到一个数据写的次数远多于读的次数的时候为了并发的读就使用读写锁。使用的时候看起来合理但是必须在一种特殊的情况下,锁的时间很长,如果每次锁的时间很段还是用读写锁,那么使用读写锁的效率还不如使用一般的锁。为什么呢?因为读写锁自己有一个状态更新的时候必须是原子操作,而在目前缺少精妙而且节省空间的同步原语的情况下,读写锁用一个字节的内存存储读的次数。因为这个数字必须被原子的更新,获取读锁的时候与获取mutex有着同样的总线事务,而且竞争造成的开销几乎一样。 通过上面的解释可以发现,使用读写锁,如果与mutex获取锁次数一样的话,从机器性能上的开销基本是一样的,而且还可能比mutex稍差一点,可为什么还是用读写锁呢?在一些特殊情况下,获取锁的时间比较长,这个时候用读写所就可以让多个线程并发的去读,从而提高处理效率,但是这些特殊的任务还要满足另外一个特点“读的次数远多于写”,因为如果读写次数差不多的话,一次读一次写,刚读一次,就要写,阻塞了其他的读,这个时候并没有“读并发”而且从锁效率上看,不如一般的mutex,也就是说如果不满足“读的次数远多于写”那么就不能发挥的写锁的特性,而且效率会比一般的锁低。如果锁持有的时间很快,读的时候只读取几十个字节的内存,还用读写锁,那么很可能会造成系统性能的下降,既然读取已经很快还有必要并发的读么?如果只是读取十几个字节还要排队竞争去读,读一次还需要个1秒钟,那么cpu一直在忙于读取(内存操作很快)和锁竞争,那么使用读锁前面的系统开销一点没有减少,反而可能因为使用读写锁会增加系统开销。
There are still many situations where long hold times (e.g., performing I/O under a lock as reader) more than pay for any memory contention, but one should be sure to gather data to make sure that it is having the desired effect on scalability. Even in those situations where a readers/writer lock is appropriate, an additional note of caution is warranted around blocking semantics. If, for example, the lock implementation blocks new readers when a writer is blocked (a common paradigm to avoid writer starvation), one cannot recursively acquire a lock as reader: if a writer blocks between the initial acquisition as reader and the recursive acquisition as reader, deadlock will result when the recursive acquisition is blocked. All of this is not to say that readers/writer locks shouldn’t be used—just that they shouldn’t be romanticized.
读写锁使用能提高效率的场景与多线程类似,需要在较长时间持有锁的情况下,如果特别长,干脆开个线程,而且读写锁还要求,读次数远多于写。对于第二段说明的死锁的情况,“If, for example, the lock implementation blocks new readers when a writer is blocked (a common paradigm to avoid writer starvation), one cannot recursively acquire a lock as reader: if a writer blocks between the initial acquisition as reader and the recursive acquisition as reader, deadlock will result when the recursive acquisition is blocked.”验证代码如下,就是读锁在递归调用(重入的时候),如果有写锁,那么会发生死锁,发生死锁的一个前提是写锁blocked的时候,不在允许新的读锁acquire(否则可能写所一直无法写,造成写饥饿的状态),这个也是读写锁使用时候需要特别注意的地方(避免读锁重入!)。假设一个调用的过程为
rwlock.acquire_read();
rwlock.acquire_read();
rwlock.release();
rwlock.release();
如果在第一次rwlock.acquire_read()后另外一个线程有rwlock.acquire_write()操作,而发生这个操作后“blocks new readers”,也就是第二次的rwlock.acquire_read()被阻塞住了,这个时候就发生了死锁,acquire_write()会让第二个rwlock.acquire_read();阻塞住,而第一个rwlock.acquire_read();也无法release,这个时候就会有死锁。验证的代码如下(使用ACE):
class CRWLockTest : public ACE_Task_Base
{
public:
virtual int svc (void)
{
cout<<"svc() - Start"<<endl;
Get();
return 0;
}
int Get()
{
cout<<"Get() - Begin acquire read..."<<endl;
m_lockRW.acquire_read();
cout<<"Get() - Acquire success."<<endl;
cout<<"Read() - Begin sleep..."<<endl;
/// 等待读锁读
ACE_OS::sleep(5);
cout<<"Read() - End sleep..."<<endl;
/// 递归调用读锁
Read();
cout<<"Get() - Begin release..."<<endl;
m_lockRW.release();
cout<<"Get() - Release success."<<endl;
return 0;
}
int Read()
{
cout<<"Read() - Begin acquire read..."<<endl;
m_lockRW.acquire_read();
cout<<"Read() - Acquire success."<<endl;
cout<<"Read() - Begin release..."<<endl;
m_lockRW.release();
cout<<"Read() - Release success."<<endl;
return 0;
}
int Write()
{
cout<<"Write() - Begin acquire write..."<<endl;
m_lockRW.acquire_write();
cout<<"Write() - Acquire success."<<endl;
cout<<"Write() - Begin release..."<<endl;
m_lockRW.release();
cout<<"Write() - Release success."<<endl;
return 0;
}
private:
ACE_RW_Thread_Mutex m_lockRW;
};
int ACE_TMAIN (int argc, ACE_TCHAR *agrv[])
{
CRWLockTest rwLockTest;
/// 开线程,调用svc方法,读锁
rwLockTest.activate();
/// Sleep为了先让rwLockTest线程中Get()方法先使用读锁
ACE_OS::sleep(2);
/// 发生死锁
rwLockTest.Write();
ACE_OS::sleep(1000);
return 0;
}