读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。比互斥量有更高的并行性。
1) 写加锁状态时,在这个锁解除之前,阻塞所有试图对这个锁加锁的线程
2) 读加锁状态时,所有试图读模式加锁的线程可以得到控制权,但是试图写模式加锁的线程被阻塞
3) 为了避免读模式长期占有锁,通常实现,在有写模式试图占有锁的情况下,后面以读模式占有锁的线程将被阻塞
适用场景:对数据的读的次数远大于写的次数的情况。
和互斥量一样,读写锁在使用之前必须初始化,在释放他们的底层的内存前必须销毁。
#include
int pthread_rwlock_init(pthread_rwlock_t* rwlock, const pthread_rwlockattr_t* attr);
int phtread_rwlock_destroy(pthread_rwlock_t* rwlock);
返回值:若成功返回0,否则返回错误编码
占有锁和解锁的函数原型如下
#include
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
返回值:若成功则返回0,否则返回错误编码
在实现读写锁的时候可能会对共享模式下可获取的锁的数量进行限制,所以需要检查pthread_rwlock_rdlock的返回值。即使pthread_rwlock_wrlock和pthread_rwlock_unlock有错误的返回值,如果锁设计合理的话,也不需要检查其返回值。错误返回值的定义只是针对不正确的使用读写锁的情况,例如未经初始化的锁,或者试图获取已拥有的锁从而可能产生死锁这样的错误返回等。
尝试获取锁的函数原型如下
#include
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
返回值:若成功则返回0,否则返回错误编码
可以获取锁的时候,函数返回0;否则,返回错误EBUSY。
应用实例,作业请求队列由单个读写锁保护,实现多个工作线程获取由单个线程分配给他们的作业。
#include
#include
struct job{
struct job* j_next;
struct job* j_prev;
pthread_t j_id; // tells which thread hanldes this job
// more stuff here
};
struct queue {
struct job* q_head;
struct job* q_tail;
pthread_rwlock_t q_lock;
};
// initialize a queue
int queue_init(struct queue* qp)
{
int err;
qp->q_head = NULL;
qp->q_tail = NULL;
err = pthread_rwlock_init(&qp->q_lock,NULL);
if(err != 0)
return err;
/* …continue init … */
return 0;
}
// insert a job at the head of the queue
job_insert(struct queue* qp,struct job* jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = qp->q_head;
jp->j_prev = NULL;
if(qp->q_head != NULL)
qp->q_head->j_prev = jp;
else
qp->q_tail = jp;
qp->q_head = jp;
pthread_rwlock_unlock(&qp->q_lock);
}
// append a job on the tail of the queue
void job_append(struct queue* qp,struct job* jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = NULL;
jp->j_prev = qp->q_tail;
if(qp->q_tail != NULL)
qp->q_tail->j_next = jp;
else
qp->q_head = jp;
qp->q_tail = jp;
pthread_rwlock_unlock(&qp->q_lock);
}
// remove the given job from a queue
void job_remove(struct queue* qp,struct job* jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
if(jp == qp->q_head){
qp->q_head = jp->j_next;
qp->q_head->j_prev = NULL;
if(qp->q_tail == jp)
qp->q_tail = NULL;
} else if(jp == qp->q_tail){
qp->q_tail = jp->j_prev;
qp->q_tail->j_next = NULL;
if(qp->q_head == jp)
qp->q_head = NULL;
} else {
jp->j_prev->j_next = jp->j_next;
jp->j_next->j_prev = jp->j_prev;
}
pthread_rwlock_unlock(&qp->q_lock);
}
// find a job for the given thread ID
struct job* job_find(struct queue* qp, pthread_t id)
{
struct job* jp;
if(pthread_rwlock_rdlock(&qp->q_lock) != 0)
return NULL;
for(jp = qp->q_head;jp != NULL; jp= jp->j_next){
if(pthread_equal(jp->j_id,id))
break;
}
pthread_rwlock_unlock(&qp->q_lock);
return jp;
}
在这个例子中,不管什么时候需要增加一个作业到队列中或者从队列中删除作业,都用写模式锁住队列的锁。不管何时搜索队列,首先需要获取读模式下的锁,允许所有的线程并发的搜索队列。在这样情况下,只有线程搜索队列的频率远远高于增加或者删除作业时,使用读写锁才可能改善性能。