读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步,
和互斥量不同的是:互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否允许线程进入。
相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁。
读写锁的使用规则:
● 只要没有写模式下的加锁,任意线程都可以进行读模式下的加锁;
● 只有读写锁处于不加锁状态时,才能进行写模式下的加锁;
读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁非常适合读数据的频率远大于写数据的频率从的应用中。这样可以在任何时刻运行多个读线程并发的执行,给程序带来了更高的并发度。
需要提到的是:读写锁到目前为止仍然不是属于POSIX标准,本文讨论的读写锁函数都是有Open Group定义的的。例如下面是在我机器上,编译器是gcc version 4.4.6,关于读写锁的定义是包含在预处理命令中的:
#if defined __USE_UNIX98 || defined __USE_XOPEN2K ... 读写锁相关函数声明... #endif |
1、读写锁的初始化和销毁
/* Initialize read-write lock */ int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock, __const pthread_rwlockattr_t *__restrict __attr); /* Destroy read-write lock */ extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否则返回错误代码 |
上面两个函数分别由于读写锁的初始化和销毁。和互斥量,条件变量一样,如果读写锁是静态分配的,可以通过常量进行初始化,如下:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
也可以通过pthread_rwlock_init()进行初始化。对于动态分配的读写锁由于不能直接赋值进行初始化,只能通过这种方式进行初始化。pthread_rwlock_init()第二个参数是读写锁的属性,如果采用默认属性,可以传入空指针NULL。
那么当不在需要使用时及释放(自动或者手动)读写锁占用的内存之前,需要调用pthread_rwlock_destroy()进行销毁读写锁占用的资源。
2、互斥锁的属性设置
/* 初始化读写锁属性对象 */ int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr); /* 销毁读写锁属性对象 */ int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr); /* 获取读写锁属性对象在进程间共享与否的标识*/ int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t * __restrict __attr, int *__restrict __pshared); /* 设置读写锁属性对象,标识在进程间共享与否 */ int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr, int __pshared); 返回值:成功返回0,否则返回错误代码 |
这个属性设置和互斥量的基本一样,具体可以参考互斥量的设置互斥量的属性设置
3、互斥锁的使用
/* 读模式下加锁 */ int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock); /* 非阻塞的读模式下加锁 */ int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限时等待的读模式加锁 */ int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 写模式下加锁 */ int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock); /* 非阻塞的写模式下加锁 */ int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限时等待的写模式加锁 */ int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 解锁 */ int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否则返回错误代码 |
(1)pthread_rwlock_rdlock()系列函数
pthread_rwlock_rdlock()用于以读模式即共享模式获取读写锁,如果读写锁已经被某个线程以读模式占用,那么调用线程就被阻塞。在实现读写锁的时候可以对共享模式下锁的数量进行限制(目前不知如何限制)。
pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一区别就是,在无法获取读写锁的时候,调用线程不会阻塞,会立即返回,并返回错误代码EBUSY。
pthread_rwlock_timedrdlock()是限时等待读模式加锁,时间参数struct timespec * __restrict __abstime也是绝对时间,和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait() 3条件变量的使用
(2)pthread_rwlock_wrlock()系列函数
pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,如果读写锁已经被其他线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入阻塞状态。
pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会立即返回,并返回错误代码EBUSY。
pthread_rwlock_timedwrlock()是限时等待写模式加锁,也和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait()3条件变量的使用。
(3)pthread_rwlock_unlock()
无论以共享模式还是独占模式获得的读写锁,都可以通过调用pthread_rwlock_unlock()函数进行释放该读写锁。
下面是测试代码:
#include <iostream> #include <cstdlib> #include <unistd.h> #include <pthread.h> using namespace std; struct{ pthread_rwlock_t rwlock; int product; }sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0}; void * produce(void *ptr) { for (int i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i; pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); } } void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } void * consume2(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume2:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, produce, NULL); pthread_create(&tid2, NULL, consume1, NULL); pthread_create(&tid3, NULL, consume2, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); pthread_join(tid3, &retVal); return 0; } |
测试结果如下:
consume1:0 consume2:0 consume2:0 consume1:1 consume2:1 consume1:2 consume2:2 consume1:3 consume2:3 consume1:4 |
如果把consume1的解锁注释掉,如下:
void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; //pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } |
程序的执行结果如下:
consume1:0 consume2:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 |
从执行结果可以看出Open Group提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的情况。