读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步,
和互斥量不同的是:互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否允许线程进入。
相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁。
读写锁的使用规则:
读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁非常适合读数据的频率远大于写数据的频率从的应用中。这样可以在任何时刻运行多个读线程并发的执行,给程序带来了更高的并发度。
需要提到的是:读写锁到目前为止仍然不是属于POSIX标准,本文讨论的读写锁函数都是有Open Group定义的的。例如下面是在我机器上,编译器是gcc version 4.4.6,关于读写锁的定义是包含在预处理命令中的:
上面两个函数分别由于读写锁的初始化和销毁。和互斥量,条件变量一样,如果读写锁是静态分配的,可以通过常量进行初始化,如下:
也可以通过pthread_rwlock_init()进行初始化。对于动态分配的读写锁由于不能直接赋值进行初始化,只能通过这种方式进行初始化。pthread_rwlock_init()第二个参数是读写锁的属性,如果采用默认属性,可以传入空指针NULL。
那么当不在需要使用时及释放(自动或者手动)读写锁占用的内存之前,需要调用pthread_rwlock_destroy()进行销毁读写锁占用的资源。
(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()
(2)pthread_rwlock_wrlock()系列函数
pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,如果读写锁已经被其他线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入阻塞状态。
pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会立即返回,并返回错误代码EBUSY。
pthread_rwlock_timedwrlock()是限时等待写模式加锁,也和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait()
(3)pthread_rwlock_unlock()
无论以共享模式还是独占模式获得的读写锁,都可以通过调用pthread_rwlock_unlock()函数进行释放该读写锁。
测试代码;
/* * pthread_rdlock.c * */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> struct share{ pthread_rwlock_t rwlock; int product; }; //把共享数据和它们的同步变量集合到一个结构中,这往往是一个较好的编程技巧。 struct share *shareData; void *produce(void *); void *consume1(void *); void *consume2(void *); int main(void) { pthread_t pid,cid1,cid2; int res; void *ret; srand(time(NULL)); shareData = malloc(sizeof(struct share)); if(shareData == NULL) { perror("shareData malloc fail"); exit(1); } res = pthread_rwlock_init(&shareData->rwlock,NULL); if(res != 0) { perror("rwlock init fail"); exit(1); } res = pthread_create(&pid,NULL,produce,NULL); if(res != 0) { perror("pthread produce create fail"); exit(1); } res = pthread_create(&cid1,NULL,consume1,NULL); if(res != 0) { perror("pthread consume1 create fail"); exit(1); } res = pthread_create(&cid2,NULL,consume2,NULL); if(res != 0) { perror("pthread consume2 create fail"); exit(1); } res = pthread_join(pid,&ret); if(res == 0) printf("producer exit\n"); res = pthread_join(cid1,&ret); if(res == 0) printf("consumer1 exit\n"); res = pthread_join(cid2,&ret); if(res == 0) printf("consumer2 exit\n"); pthread_rwlock_destroy(&shareData->rwlock); exit(0); } void *produce(void *arg) { int i; for(i=0;i<6;i++) { pthread_rwlock_wrlock(&shareData->rwlock); shareData->product = rand()%100+1; printf("produce:%d\n",shareData->product); pthread_rwlock_unlock(&shareData->rwlock); sleep(1); } pthread_exit(NULL); } void *consume1(void *arg) { int i; for(i=0;i<6;i++) { pthread_rwlock_rdlock(&shareData->rwlock); printf("consume1:%d\n",shareData->product); pthread_rwlock_unlock(&shareData->rwlock); sleep(1); } pthread_exit(NULL); } void *consume2(void *arg) { int i; for(i=0;i<6;i++) { pthread_rwlock_rdlock(&shareData->rwlock); printf("consume2:%d\n",shareData->product); pthread_rwlock_unlock(&shareData->rwlock); sleep(1); } pthread_exit(NULL); }
consume2:0
consume1:0
produce:10
consume1:10
consume2:10
produce:23
consume1:23
consume2:23
produce:4
consume2:4
consume1:4
produce:55
consume1:55
consume2:55
produce:87
consume2:87
consume1:87
produce:63
producer exit
consumer1 exit
consumer2 exit
注意:如果要实现生产-消费者模型,则消费者线程还应该加上条件变量,当共享区域没有数据时,阻塞等待。
如果把consume1的解锁注释掉,如下:
void *consume1(void *arg) { int i; for(i=0;i<6;i++) { pthread_rwlock_rdlock(&shareData->rwlock); printf("consume1:%d\n",shareData->product); //pthread_rwlock_unlock(&shareData->rwlock); sleep(1); } pthread_exit(NULL); }运行结果:
consume2:0
consume1:0
consume2:0
consume1:0
consume1:0
consume2:0
consume2:0
consume1:0
consume1:0
consume2:0
consume2:0
consume1:0
可以看出,当生产者线程试图以写模式加锁时,由于consume1没有释放读锁,所以会一直阻塞;而consume2以读模式则依然可以成功加锁。