读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步,
互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有三种状态:读模式下的加锁状态,写模式下的加锁状态,不加锁状态。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
- 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁(读或写)的线程都会被阻塞。
- 当读写锁在读加锁状态时,所有试图以读模式对它加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。
- 当读写锁在读加锁状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。(后面有代码验证发现ubuntu 10.04系统下,不会阻塞随后的读模式的请求,最终导致请求写锁的线程饿死状态)这样可以避免读模式锁长期占有,而等待的写模式锁请求一直得不到满足,出现饿死情况。后面会测试ubuntu 10.04系统的情况。(注意前篇关于记录锁fcntl中,ubuntu 10.04系统下,进程拥有读锁,然后优先处理后面的读锁,再处理写锁,导致写锁出现饿死)
读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁非常适合读数据的频率远大于写数据的频率从的应用中。这样可以在任何时刻运行多个读线程并发的执行,给程序带来了更高的并发度。
需要提到的是:读写锁到目前为止仍然不是属于POSIX标准。
1读写锁的初始化和销毁
#include <pthread.h>int pthread_rwlock_init (pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);与互斥量一样,读写锁在使用之前必须初始化,在释放它们底层的内存前必须销毁。
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_rwlock_rdlock (pthread_rwlock_t *rwlock);/* 非阻塞的读模式下加锁 */int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);/* 限时等待的读模式加锁 */int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,const struct timespec *abstime);/* 写模式下加锁 */int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);/* 非阻塞的写模式下加锁 */int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);/* 限时等待的写模式加锁 */int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,const struct timespec *abstime);/* 解锁 */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_rdlock/pthread_rwlock_tryrdlock,则结果是不确定的。
pthread_rwlock_timedrdlock()是限时等待读模式加锁,时间参数struct timespec * abstime也是绝对时间。
(2)pthread_rwlock_wrlock()系列函数
pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,如果读写锁已经被其他线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入阻塞状态。
pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会立即返回,并返回错误代码EBUSY。
针对未初始化的读写锁调用pthread_rwlock_wrlock/pthread_rwlock_trywrlock,则结果是不确定的。
pthread_rwlock_timedwrlock()是限时等待写模式加锁。
(3)pthread_rwlock_unlock()
无论以共享模式还是独占模式获得的读写锁,都可以通过调用pthread_rwlock_unlock()函数进行释放该读写锁。
针对未初始化的读写锁调用pthread_rwlock_unlock,则结果是不确定的。
注意:当读写锁以读模式被占有N次,即调用pthread_rwlock_rdlock() N次,且成功。则必须调用N次pthread_rwlock_unlock()才能执行匹配的解锁操作。
示例代码:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <errno.h>#define MAXDATA 1024#define MAXREDER 100#define MAXWRITER 100struct{ pthread_rwlock_t rwlock; //读写锁 char datas[MAXDATA]; //共享数据域}shared = { PTHREAD_RWLOCK_INITIALIZER};void *reader(void *arg);void *writer(void *arg);int main(int argc,char *argv[]){ int i,readercount,writercount; pthread_t tid_reader[MAXREDER],tid_writer[MAXWRITER]; if(argc != 3) { printf("usage : <reader_writer> #<readercount> #<writercount>\n"); exit(0); } readercount = atoi(argv[1]); //读者个数 writercount = atoi(argv[2]); //写者个数 pthread_setconcurrency(readercount+writercount); for(i=0;i<writercount;++i) pthread_create(&tid_writer[i],NULL,writer,NULL); sleep(1); //等待写者先执行 for(i=0;i<readercount;++i) pthread_create(&tid_reader[i],NULL,reader,NULL); //等待线程终止 for(i=0;i<writercount;++i) pthread_join(tid_writer[i],NULL); for(i=0;i<readercount;++i) pthread_join(tid_reader[i],NULL); exit(0);}void *reader(void *arg){ pthread_rwlock_rdlock(&shared.rwlock); //获取读出锁if( pthread_rwlock_rdlock(&shared.rwlock) ==0 ) //获取读出锁 printf("pthread_rwlock_rdlock OK\n"); printf("Reader begins read message.\n"); printf("Read message is: %s\n",shared.datas); pthread_rwlock_unlock(&shared.rwlock); //释放锁if( pthread_rwlock_unlock(&shared.rwlock) != 0 ); printf("pthread_rwlock_unlock fail\n"); return NULL;}void *writer(void *arg){ char datas[MAXDATA]; pthread_rwlock_wrlock(&shared.rwlock); //获取写锁if( pthread_rwlock_wrlock(&shared.rwlock) != 0)//再次获取写锁 perror("pthread_rwlock_wrlock");if( pthread_rwlock_rdlock(&shared.rwlock) !=0 ) //获取读出锁 perror("pthread_rwlock_rdlock"); printf("Writers begings write message.\n");sleep(1); printf("Enter the write message: \n"); scanf("%s",datas); //写入数据 strcat(shared.datas,datas); pthread_rwlock_unlock(&shared.rwlock); //释放锁 return NULL;}运行结果:
huangcheng@ubuntu:~$ gcc 2.c -lpthreadhuangcheng@ubuntu:~$ ./a.out 5 3pthread_rwlock_wrlock: Successpthread_rwlock_rdlock: SuccessWriters begings write message.Enter the write message:hupthread_rwlock_wrlock: Successpthread_rwlock_rdlock: SuccessWriters begings write message.Enter the write message:1pthread_rwlock_wrlock: Successpthread_rwlock_rdlock: SuccessWriters begings write message.Enter the write message:2pthread_rwlock_rdlock OKReader begins read message.Read message is: hu12pthread_rwlock_unlock failpthread_rwlock_rdlock OKReader begins read message.Read message is: hu12pthread_rwlock_unlock failpthread_rwlock_rdlock OKReader begins read message.Read message is: hu12pthread_rwlock_unlock failpthread_rwlock_rdlock OKReader begins read message.Read message is: hu12pthread_rwlock_unlock failpthread_rwlock_rdlock OKReader begins read message.Read message is: hu12pthread_rwlock_unlock fail
结果说明: (1)当一个线程获得读写锁的写模式,其他线程试图获得该读写锁的读模式或者是写模式,都将会阻塞,直到该线程释放该读写锁。
(2)当一个线程获得读写锁的写模式,该线程试图获得该读写锁的读模式或写模式,都会立即返回失败,不会导致失败。
(3)当一个线程获得读写锁的读模式,如果该线程试图获得该读写锁的读模式,则返回成功,并在该线程释放该读写锁只需要释放一次,第二次会释放失败。
(4)当一个线程获得读写锁的读模式,且还没有释放该读写锁,如果该线程试图获得该读写锁的写模式,将导致阻塞,直到该线程释放该读写锁。
(5)当一个线程获得读写锁的写模式,该线程试图释放该写锁两次,将导致不可预测的问题。
(6)当一个线程获得读写锁的写模式或者读模式,不能读该读写锁释放两次。即不管该线程获得读锁几次,都只需要释放该读写锁一次就OK。
3读写锁的属性设置
/* 初始化读写锁属性对象 */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); 返回值:成功返回0,否则返回错误代码
pthread_rwlockattr_setpshared()函数的第二个参数pshared用于设定是否进程间共享,其值可以是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,后者是设置进程间共享。
示例代码:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <errno.h>struct{ pthread_rwlock_t rwlock; int product;}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};void * produce(void *ptr){ int i; for ( i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i;printf("produce:%d\n",i); pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); }}void * consume1(void *ptr){ int i;for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume1:%d\n",sharedData.product); pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); }}void * consume2(void *ptr){ int i;for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume2:%d\n",sharedData.product); 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;}运行结果:
huangcheng@ubuntu:~$ ./a.outconsume2:0consume1:0produce:0consume2:0consume1:0produce:1consume2:1consume1:1produce:2consume2:2consume1:2produce:3consume2:3consume1:3produce:4huangcheng@ubuntu:~$
如果把 consume1 的解锁注释掉,如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <errno.h>struct{ pthread_rwlock_t rwlock; int product;}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};void * produce(void *ptr){ int i; for ( i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i;printf("produce:%d\n",i); pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); }}void * consume1(void *ptr){ int i;for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume1:%d\n",sharedData.product); // pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); }}void * consume2(void *ptr){ int i;for ( i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); printf("consume2:%d\n",sharedData.product); 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;}程序运行结果:
huangcheng@ubuntu:~$ ./a.outconsume2:0consume1:0consume2:0consume1:0consume2:0consume1:0consume2:0consume1:0consume2:0consume1:0最后程序保持阻塞状态从执行结果可以看出Ubuntu 10.04提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的情况。