读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于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 100
struct
{
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 -lpthread
huangcheng@ubuntu:~$ ./a.out 5 3
pthread_rwlock_wrlock: Success
pthread_rwlock_rdlock: Success
Writers begings write message.
Enter the write message:
hu
pthread_rwlock_wrlock: Success
pthread_rwlock_rdlock: Success
Writers begings write message.
Enter the write message:
1
pthread_rwlock_wrlock: Success
pthread_rwlock_rdlock: Success
Writers begings write message.
Enter the write message:
2
pthread_rwlock_rdlock OK
Reader begins read message.
Read message is: hu12
pthread_rwlock_unlock fail
pthread_rwlock_rdlock OK
Reader begins read message.
Read message is: hu12
pthread_rwlock_unlock fail
pthread_rwlock_rdlock OK
Reader begins read message.
Read message is: hu12
pthread_rwlock_unlock fail
pthread_rwlock_rdlock OK
Reader begins read message.
Read message is: hu12
pthread_rwlock_unlock fail
pthread_rwlock_rdlock OK
Reader begins read message.
Read message is: hu12
pthread_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.out
consume2:0
consume1:0
produce:0
consume2:0
consume1:0
produce:1
consume2:1
consume1:1
produce:2
consume2:2
consume1:2
produce:3
consume2:3
consume1:3
produce:4
huangcheng@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.out
consume2:0
consume1:0
consume2:0
consume1:0
consume2:0
consume1:0
consume2:0
consume1:0
consume2:0
consume1:0
最后程序保持阻塞状态

从执行结果可以看出Ubuntu 10.04提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的情况。

UNIX环境高级编程——线程同步之读写锁以及属性的更多相关文章

  1. UNIX环境高级编程——线程同步之条件变量以及属性

    条件变量变量也是出自POSIX线程标准,另一种线程同步机制.主要用来等待某个条件的发生.可以用来同步同一进程中的各个线程.当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来 ...

  2. UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)

    一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...

  3. UNIX环境高级编程——线程同步之互斥量

    互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程.当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步. 互斥量,从字面上就可以知道 ...

  4. UNIX环境高级编程——线程属性

    pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...

  5. Unix 环境高级编程---线程创建、同步、

    一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...

  6. UNIX环境高级编程——线程和fork

    当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...

  7. UNIX环境高级编程——线程

    线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID.一组寄存器值.栈.调度优先级和策略.信号屏蔽字.errno变量以及线程私有数据. 进程的所有信息对该进程的所有线程都是共享的, ...

  8. UNIX环境高级编程——线程和信号

    每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的.这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变.这样如果一 ...

  9. UNIX环境高级编程——线程私有数据

    线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制. 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变 ...

随机推荐

  1. Enum枚举

    Java Enum原理 public enum Size{ SMALL, MEDIUM, LARGE, EXTRA_LARGE }; 实际上,这个声明定义的类型是一个类,它刚好有四个实例,在此尽量不要 ...

  2. mongoose多条件模糊查询实例

    mongoose多条件模糊查询 这是今天手头项目中遇到的一个问题,关于mongoose如何实现类似于SQL中 nick LIKE '%keyword%' or email LIKE '%keyword ...

  3. TensorFlow入门和示例分析

    本文以TensorFlow源码中自带的手写数字识别Example为例,引出TensorFlow中的几个主要概念.并结合Example源码一步步分析该模型的实现过程. 一.什么是TensorFlow 在 ...

  4. MFC误报内存泄露的修复

    在debug状态退出程序的时候,VS会在输出窗口列出可能的内存泄露的地方. MFC中使用DEBUG_NEW能够更方便的定位泄露的地点.但假如MFC的dll释放""过早"& ...

  5. Most Common Solutions to FRM-41839 and .tmp Files Not Being Deleted

    In this Document   Symptoms   Changes   Cause   Solution   References APPLIES TO: Oracle Application ...

  6. Appium webdriver的capabilities配置

    Capabilities是由客户端发送给Appium服务器端的用来告诉服务器去启动哪种我们想要的会话的一套键值对集合.当中也有一些键值对是用来在自动化的过程中修改服务器端的行为方式. 必填的项目: d ...

  7. 为什么函数式编程可以没有while?

    以前想不通,今天在写代码时不知怎么的,偶然就发现了答案.. 比如说把某个字符串s中所有"00"及更长的'00'统统换为'0'.最后结果中不能包含'00'. 00001100--&g ...

  8. Redis集群功能预览

    目前Redis Cluster仍处于Beta版本,Redis 3.0将会加入,在此可以先对其主要功能和原理进行一个预览.参考<Redis Cluster - a pragmatic approa ...

  9. [安全]Back_Track_5 vm 版安装和使用

    下载安装 下载使用国内的镜像  http://mirrors.ustc.edu.cn/kali-images/kali-1.0.9/ 我这里是vm9.0 下载之后解压,然后打开vm,然后 文件--&g ...

  10. paypal的IPN机制

    paypal对接时发现有这么一个机制,看起来还不错,起到了防止篡改欺诈行为,保证了通信的安全性,但会增加几次通信.