一、读写锁的概念
- 读写锁与互斥量类似。但是互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁
- 不过读写锁允许更高的并行性,而且有更多的状态
读写锁可以有3种状态:
- ①读模式下加锁
- ②写模式下加锁
- ③不加锁状态
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁
二、读写锁的使用
- 当读写锁是写加锁状态下,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会阻塞
- 当读写锁是读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止
虽然各操作系统对读写锁的实现各不同,但当读写锁处于读模式锁住的状态,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足
读写锁的应用:读写锁非常适合于对数据结构读的次数大于写的情况
- 当读写锁在写模式下时,它所保护的数据结构就可以被安全的修改,因为一次只有一个线程可以在写模式下拥有这个锁
- 当读写锁在读模式下时,只要线程先获取了读模式下的读写锁,该锁所保护的数据结构就可以被多个获得读模式锁的线程读取
三、读写锁别名
读写锁也称为共享互斥锁
- 当读写锁是读模式锁住时,就可以说成是以共享模式锁住的
- 当它是写模式锁住时,就可以说成是以互斥模式锁住的
四、读写锁的初始化
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 返回值:成功返回0,否则返回错误编号
与互斥量相比:读写锁在使用之前必须初始化,在释放它们的底层内存之前必须销毁
pthread_rwlock_init:对读写锁进行初始化
- 参数1:初始化的读写锁
- 参数2:读写锁初始化时的属性。如果用默认属性,此处填NULL
pthread_rwlock_destory:
- 如果pthread_rwlock_init为读写锁分配了资源,那么pthread_rwlock_destory将释放这些资源
- 如果在pthread_rwlock_destory之前就释放了读写锁占用的内存空间,那么分配给这个锁的资源就会丢失
常量PTHREAD_RWLOCK_INITIALIZER:
- Single UNIX Specification在XSI扩展中定义了此常量。如果默认属性足够的话,可以用它对静态分配的读写锁进行初始化
五、读写锁的加锁与解锁函数
#include <pthread.h>
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:不管以何种方式锁住读写锁,都可以用这个函数解锁
返回值的注意事项:
- 各种实现可能会对共享模式下可获取的读写锁的次数进行限制,所以需要检查pthread_rwlock_rdlock的返回值
- 即使pthread_rwlock_wrlock和pthread_rwlock_unlock有错误返回,而且从技术上来讲,在调用函数时应该总会检查错误返回,但是如果锁设计合理的话,就不需要检查它们。错误返回值的定义只是针对不正确使用读写锁的情况(如未经初始化的锁),或者试图获取已拥有的锁从而可能产生死锁的情况
- 但是还需要注意,有些特定的实现可能会定义另外的错误返回
#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //返回值:成功返回0;否则返回错误编号
这两个函数类似于pthread_mutex_trylock函数:
- pthread_rwlock_tryrdlock:尝试获得读模式的读写锁,如果可以获取返回0,不可以获取出错返回EBUSY
- pthread_rwlock_trywrlock:尝试获得写模式的读写锁,如果可以获取返回0,不可以获取出错返回EBUSY
这两个函数可以用于我们前面讨论的遵守某种锁层次但还不能完全避免死锁的情况
六、案例
#include <stdlib.h>
#include <pthread.h>
struct job {
struct job *j_next;
struct job *j_prev;
pthread_t j_id; /* tells which thread handles 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 initialization ... */
return(0);
}
//Insert a job at the head of the queue.
void 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; /* list was empty */
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; /* list was empty */
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;
if (qp->q_tail == jp)
qp->q_tail = NULL;
else
jp->j_next->j_prev = jp->j_prev;
} else if (jp == qp->q_tail) {
qp->q_tail = jp->j_prev;
jp->j_prev->j_next = jp->j_next;
} 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);
}
七、带有超时的读写锁加锁
#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict tsptr);
//返回值:成功返回0;否则返回错误编号
- 与互斥量一样,Single UNIX Specification提供了带有超时的读写锁加锁函数,使应用程序在获取读写锁时避免陷入永久阻塞状态。