在以前的一篇博文Linux多线程编程初探中,只提到了用于线程同步的互斥锁、条件变量,而没有提及读写锁(read-write lock)。

  本文主要整理自以下文章:

  读写锁(read-write lock)机制-----多线程同步问题的解决

  请用普通的互斥锁编程实现一个读写锁

读写锁

  读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。

  1)当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.

  2)当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;

  3)当读写锁在读模式锁状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞;

  这种锁适用对数据结构进行读的次数比写的次数多的情况。

读写锁API

初始化和销毁

  对于读写锁变量的初始化可以有两种方式,一种是通过给一个静态分配的读写锁赋予常值PTHREAD_RWLOCK_INITIALIZER来初始化它,另一种方法就是通过调用pthread_rwlock_init()来动态的初始化。而当某个线程不再需要读写锁的时候,可以通过调用pthread_rwlock_destroy来销毁该锁。函数原型如下:

#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_destroy对读写锁进行清理,释放由pthread_rwlock_init所分配的资源。在初始化某个读写锁的时候,如果属性指针attr是个空指针的话,表示默认的属性;如果想要使用非默认属性,则要使用到下面的两个函数:

#include 
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockatttr_t *attr);
// 成功返回0,出错则返回错误码。

  这里还需要说明的是,当初始化读写锁完毕以后呢,该锁就处于一个非锁定状态。数据类型为pthread_rwlockattr_t的某个属性对象一旦初始化了,就可以通过不同的函数调用来启用或者是禁用某个特定的属性。

读加锁和写加锁

  读写锁的数据类型是pthread_rwlock_t,如果这个数据类型中的某个变量是静态分配的,那么可以通过给它赋予常值PTHREAD_RWLOCK_INITIALIZAR来初始化它。pthread_rwlock_rdlock()用来获取读出锁,如果相应的读出锁已经被某个写入者占有,那么就阻塞调用线程。pthread_rwlock_wrlock()用来获取一个写入锁,如果相应的写入锁已经被其它写入者或者一个或多个读出者占有,那么就阻塞该调用线程;pthread_rwlock_unlock()用来释放一个读出或者写入锁。函数原型如下:

#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_tryrdlock()和pthread_rwlock_trywrlock(),非阻塞方式下获取锁的时候,如果不能马上获取到,就会立即返回一个EBUSY错误,而不是把调用线程投入到睡眠等待。函数原型如下:

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 成功则返回0, 出错则返回错误编号.
用互斥锁实现读写锁

  伪代码如下:

 1 count_mutex = mutex_init();
 2 write_mutex = mutex_init();
 3 read_count = 0;
 4 
 5 void read_lock{
 6     lock(count_mutex);
 7     read_count++;
 8     if (read_count == 1) {
 9         lock(write_mutex);
10     }
11     unlock(count_mutex);
12 }
13 
14 void read_unlock{
15     lock(count_mutex);
16     read_count--;
17     if (read_count == 0) {
18         unlock(write_mutex);
19     }
20     unlock(count_mutex);
21 }
22 
23 void write_lock{
24     lock(write_mutex);
25 }
26 
27 void write_unlock{
28     unlock(write_mutex);
29 }