在《多线程编程之Linux环境下的多线程(二)》一文中提到了Linux环境下的多线程同步机制之一的读写锁。本文再详细写一下读写锁的概念和原理。
一、什么是读写锁
读写锁(也叫共享-独占锁)实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须“自旋”在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须“自旋”在那里,直到写者释放该读写锁。读写锁适合于对数据结构的读次数比写次数多很多的场合。
二、一种Linux环境下的实现方法
下面利用pthread.h提供的mutex和condition来实现一个读写锁:
#include <pthread.h> struct rwlock { pthread_mutex_t lock; pthread_cond_t read, write; unsigned readers, writers, read_waiters, write_waiters; }; void reader_lock(struct rwlock *self) { pthread_mutex_lock(&self->lock); if (self->writers || self->write_waiters) { self->read_waiters++; do pthread_cond_wait(&self->read, &self->lock); while (self->writers || self->write_waiters); self->read_waiters--; } self->readers++; pthread_mutex_unlock(&self->lock); } void reader_unlock(struct rwlock *self) { pthread_mutex_lock(&self->lock); self->readers--; if (self->write_waiters) pthread_cond_signal(&self->write); pthread_mutex_unlock(&self->lock); } void writer_lock(struct rwlock *self) { pthread_mutex_lock(&self->lock); if (self->readers || self->writers) { self->write_waiters++; do pthread_cond_wait(&self->write, &self->lock); while (self->readers || self->writers); self->write_waiters--; } self->writers = 1; pthread_mutex_unlock(&self->lock); } void writer_unlock(struct rwlock *self) { pthread_mutex_lock(&self->lock); self->writers = 0; if (self->write_waiters) pthread_cond_signal(&self->write); else if (self->read_waiters) pthread_cond_broadcast(&self->read); pthread_mutex_unlock(&self->lock); } void rwlock_init(struct rwlock *self) { self->readers = self->writers = self->read_waiters = self->write_waiters = 0; pthread_mutex_init(&self->lock, NULL); pthread_cond_init(&self->read, NULL); pthread_cond_init(&self->write, NULL); }
这种实现方式可以在有写锁存在的情况下不增加读锁数量,而是累加读等待数目。
三、一种Windows环境下的实现方法
在Windows环境下实现方式也是差不多的,参考如下实例:
typedef struct _RWLock { int count; int state; HANDLE hRead; HANDLE hWrite; } RWLock; typedef enum /* 枚举读写状态 */ { STATE_EMPTY = 0, STATE_READ, STATE_WRITE }; RWLock* create_read_write_lock(HANDLE hRead, HANDLE hWrite) { RWLock* pRwLock = NULL; assert(NULL != hRead && NULL != hWrite); pRwLock = (RWLock*)malloc(sizeof(RWLock)); pRwLock->hRead = hRead; pRwLock->hWrite = hWrite; pRwLock->count = 0; pRwLock->state = STATE_EMPTY; return pRwLock; } void read_lock(RWLock* pRwLock) { assert(NULL != pRwLock); WaitForSingleObject(pRwLock->hRead, INFINITE); pRwLock->count ++; if(1 == pRwLock->count){ WaitForSingleObject(pRwLock->hWrite, INFINITE); pRwLock->state = STATE_READ; } ReleaseMutex(pRwLock->hRead); } void write_lock(RWLock* pRwLock) { assert(NULL != pRwLock); WaitForSingleObject(pRwLock->hWrite, INFINITE); pRwLock->state = STATE_WRITE; } void read_write_unlock(RWLock* pRwLock) { assert(NULL != pRwLock); if(STATE_READ == pRwLock->state){ WaitForSingleObject(pRwLock->hRead, INFINITE); pRwLock->count --; if(0 == pRwLock->count){ pRwLock->state = STATE_EMPTY; ReleaseMutex(pRwLock->hWrite); } ReleaseMutex(pRwLock->hRead); }else{ pRwLock->state = STATE_EMPTY; ReleaseMutex(pRwLock->hWrite); } return; }
Windows环境下的实现方式要更加简单一些,不过这种方式的写操作一旦有读操作获取了锁,就只能等待所有读操作执行完了才行。如果一直都有读操作,那么写操作将会一直等待下去。
四、读写锁使用的经验总结
(1)读写锁的优势只有在多读少写、代码段运行时间长这两个条件下才会效率达到最大化;
(2)任何公共数据的修改都必须在锁里面完成;
(3)读写锁有自己的应用场所,选择合适的应用环境十分重要;
(4)编写读写锁很容易出错,需要多加练习;
(5)读锁和写锁一定要分开使用,否则达不到效果。