读写锁是同步的又一种形式,与互斥锁不同,互斥锁同时只能被一个线程获取,而读写锁可以同时被多个线程获取读锁,这在一定程度上提高了程序的并发性,写锁也同样是只能被一个线程获取。
读写锁的数据类型为pthread_rwlock_t,静态分配时可初始化为PTHREAD_RWLOCK_INITIALIZER,动态分配时调用函数pthread_rwlock_init完成。下面是获取与释放读写锁的几个函数。
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock获取一个读锁,如果将要获取的读锁被某个写入者持有,将会阻塞,而pthread_rwlock_tryrdlock立即返回一个EBUSY错误却不会阻塞。pthread_rwlock_wrlock获取一个写锁,如果将要获取的写锁被某个写入者或读出者持有都将发生阻塞,而pthread_rwlock_trywrlock也是立即返回EBUSY并不阻塞。pthread_rwlock_unlock用于释放某个读写锁,pthread_rwlock_destroy则用来销毁一个读写锁。
读写锁使用pthread_rwlock_init初始化时,如果attr参数为空,将使用属性缺省值,不过也可以使用下面几个读写锁属性相关的函数进行动态操作。
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int *pshared);
读写锁属性的数据类型为pthread_rwlockattr_t,pthread_rwlockattr_setpshared函数的pshared参数可以是PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED,当为后者时,读写锁可在不同进程间共享,而不局限于一个进程的不同线程间。
下面以一个例子简述读写锁的用法。
// rwlock.c
#include <pthread.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
#define MAXSIZE (10000)
struct
{
pthread_rwlock_t rwlock;
int buff[MAXSIZE];
int nindex; // next index of buff
int nvalue; // next value of buff
} shared = { PTHREAD_RWLOCK_INITIALIZER };
void* thread_write(void *arg)
{
while (1) {
pthread_rwlock_wrlock(&shared.rwlock);
if (shared.nindex >= MAXSIZE) {
printf("write thread [%ld] full\n", pthread_self());
pthread_rwlock_unlock(&shared.rwlock);
return NULL;
}
shared.buff[shared.nindex] = shared.nvalue;
shared.nindex++;
shared.nvalue++;
*((int*)arg) += 1;
pthread_rwlock_unlock(&shared.rwlock);
}
}
void* thread_read(void *arg)
{
int i;
time_t start = time(NULL);
while (1) {
pthread_rwlock_rdlock(&shared.rwlock);
for (i = 0; i < shared.nindex; ++i) {
if (shared.buff[i] != i) {
printf("read thread [%ld] error: buff[%d]=%d\n", pthread_self(), i, shared.buff[i]);
}
}
*((int*)arg) += 1;
if (shared.nindex >= MAXSIZE) {
printf("read thread [%ld] full\n", pthread_self());
pthread_rwlock_unlock(&shared.rwlock);
return NULL;
}
if (start + 5 < time(NULL)) {
printf("read thread [%ld] timeout\n", pthread_self());
pthread_rwlock_unlock(&shared.rwlock);
return NULL;
}
pthread_rwlock_unlock(&shared.rwlock);
}
}
int main(int argc, char *argv[])
{
pthread_t wrthreads[2], rdthreads[2];
int wrcount[2], rdcount[2];
int s, i;
// creates write threads
for (i = 0; i < 2; ++i) {
wrcount[i] = 0;
if ((s = pthread_create(&wrthreads[i], NULL, thread_write, (void*)&wrcount[i])) != 0) {
handle_error_en(s, "pthread_create write");
}
else {
printf("write thread [%ld] created, count [%d]\n", wrthreads[i], wrcount[i]);
}
}
// creates read threads
for (i = 0; i < 2; ++i) {
rdcount[i] = 0;
if ((s = pthread_create(&rdthreads[i], NULL, thread_read, (void*)&rdcount[i])) != 0) {
handle_error_en(s, "pthread_create read");
}
else {
printf("read thread [%ld] created, count [%d]\n", rdthreads[i], rdcount[i]);
}
}
// waiting for write threads
for (i = 0; i < 2; ++i) {
if ((s = pthread_join(wrthreads[i], NULL)) != 0) {
handle_error_en(s, "pthread_join write");
}
else {
printf("write thread [%ld] done, count [%d]\n", wrthreads[i], wrcount[i]);
}
}
// waiting for read threads
for (i = 0; i < 2; ++i) {
if ((s = pthread_join(rdthreads[i], NULL)) != 0) {
handle_error_en(s, "pthread_join read");
}
else {
printf("read thread [%ld] done, count [%d]\n", rdthreads[i], rdcount[i]);
}
}
printf("----------done----------\n");
exit(EXIT_SUCCESS);
}
例子中,handler_error_en用于错误处理,MAXSIZE为buff长度,shared是一个共享数据结构,包括读写锁rwlock、共享数据buff、指向buff下一个索引的nindex和指向buff下一个索引的值nvalue。main函数先创建两个线程用于写buff,再创建两个线程用于读buff,每个线程有一个计数器,创建线程前初始化为0,创建线程时作为pthread_create的参数传给对应的线程处理函数,最后主线程调用pthread_join等待读写线程结束并打印每个线程访问buff的次数。写线程thread_write函数在修改buff前,先获取写锁,buff索引每次递增1,其值与索引相同,同时增加参数arg值以记录修改buff的次数,当buff索引达到MAXSIZE时退出线程。读线程thread_read函数只是检查buff的索引是否与其值相同,每次访问buff时获取读锁,由于有两个读线程,它们的读锁可以被同时获取,这样有可能一直阻塞写线程,而buff的nindex却得不到更新,这样线程将无法结束,所以增加了延时机制,当时间超过5秒将自动退出,以给写线程机会写满buff。
gcc -o rwlock -pthread rwlock.c
./rwlock
下面是读线程超时的结果,可以看出,写线程共修改了buff MAXSIZE即10000次,是正确的,而读线程访问buff的次数却非常多,也说明了读锁可以被同时获取,最后直到超时而退出。
write thread [140007865181952] created, count [0]
write thread [140007856789248] created, count [0]
read thread [140007848396544] created, count [0]
read thread [140007840003840] created, count [0]
read thread [140007840003840] timeout
read thread [140007848396544] timeout
write thread [140007865181952] full
write thread [140007856789248] full
write thread [140007865181952] done, count [6019]
write thread [140007856789248] done, count [3981]
read thread [140007848396544] done, count [1071551]
read thread [140007840003840] done, count [1072573]
----------done----------
下面是读线程没有超时的结果,其结果也是合理的:写线程共修改buff 10000次,而读线程共访问buff的次数可能很多,也可能很少,这是不确定的。
write thread [139683703310080] created, count [0]
write thread [139683694917376] created, count [0]
read thread [139683686524672] created, count [0]
read thread [139683678131968] created, count [0]
write thread [139683694917376] full
read thread [139683678131968] full
read thread [139683686524672] full
write thread [139683703310080] full
write thread [139683703310080] done, count [0]
write thread [139683694917376] done, count [10000]
read thread [139683686524672] done, count [211941]
read thread [139683678131968] done, count [132537]
----------done----------