一、什么是读者写者模型?
同上篇博客中提到的生产者消费者模型类似,我们可以把读者写者模型简单的概括为“三二一”原则
所谓的“三”指的是三种关系,分别为:
(1)写者与写者:互斥关系
(2)读者与读者:既不同步也不互斥
(3)读者与写者:同步关系(读者优先或者写者优先),互斥关系(读者读完了写者才能写,写者写完了读者才能读,这样保证了数据的有效性)
所谓的“二”指的是:读者和写者
所谓的“一”指的是:一个读写场所
二、读者写者模型与生产者与消费者模型的区别
我们可以将生产者当做是读写模型中的写者,消费者当做是读写模型中的读者。
但是在生产者与消费者模型中,消费者与消费者的关系是互斥的,而对于我们的读写模型中的读者与读者是没关系的。
这两种模型的最大区别是:在生产者消费者模型中,生产者产生数据,消费者必须把数据拿走,才算是消费成功了。但是对于我们的读写模型来说,读者只需读到数据即可,它不需要拿走数据。
三、自旋锁与挂起等待锁
挂起等待锁:当线程申请资源失败时,它会挂起等待(操作系统将它挂起)。比如互斥锁和二元信号量。
自旋锁:当线程申请资源失败时,它会一直在当前检测锁位置处轮寻锁的状态来看资源是否能被申请。不断检测的过程叫作自旋(操作系统将它自旋)。
那么如何来选择这两种类型的锁呢?我们来根据线程在临界区需要等待的时间长短来进行选择,需要在临界区等待时间长的线程选择挂起等待锁,而等待时间段的线程选择自旋锁。
了解以上内容我们就来说说在读写模型中最重要的一个知识点——读写锁
读写锁是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者,但不能同时既有读者又有写者。
上述文字中提到了并发这个概念,那么什么是并发呢?并发是一段时间内因为CPU切换导致的若干线程同时运行的现象叫作并发。它是通过线程或进程的来回切换实现的。
接下来我们来看看读写锁的具体接口吧!
(1)创建与销毁读写锁——pthread_rwlock_init和pthread_rwlock_destroy
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)//创建读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁读写锁
(2)加读写锁
读者加锁——pthread_rwlock_tryrdlock
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//阻塞版本
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//非阻塞版本。成功返回值为0,失败返回错误码
写者加锁——pthread_rwlock_trywrlock
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//非阻塞版本
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//阻塞版本
(3)解锁——pthread_rwlock_unlock
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
好啦!知道了上述所有的背景知识后,我们来看看具体实现读写模型的具体代码吧!
(1)创建Makefile
myrw:myrw.c
gcc -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f myrw
(2)创建myrw.c
#include<stdio.h>
#include<pthread.h>
int book=0;//给book中写数据
pthread_rwlock_t rwlock;//定义读写锁
void *myread(void *arg)//读者
{
while(1)
{
if(pthread_rwlock_tryrdlock(&rwlock)!=0)//读者加锁失败,则说明写者正在写
{
printf("reader say:the wirter is writing...\n");
}
else
{
printf("read done...%d\n",book);
sleep(5);
pthread_rwlock_unlock(&rwlock);//解锁
}
}
}
void *mywirte(void *arg)//写者
{
sleep(1); //读者先运行
while(1)
{
if(pthread_rwlock_trywrlock(&rwlock)!=0)//写者加锁失败,则说明读者正在读
{
printf("writer say:the reader is reading...\n");
usleep(500000);//0.5s刷新一次
}
else
{
book++;
printf("write done...%d\n",book);
pthread_rwlock_unlock(&rwlock);
}
}
}
int main()
{
pthread_rwlock_init(&rwlock,NULL);//初始化读写锁
pthread_t reader,wirter;
pthread_create(&reader,NULL,myread,NULL);
pthread_create(&wirter,NULL,mywirte,NULL);
pthread_join(reader,NULL);
pthread_join(wirter,NULL);
pthread_rwlock_destroy(&rwlock);//销毁读写锁
return 0;
}
以上的代码是读者先读的情况!程序运行成功的结果如图:
写者先写的情况只需将myread与mywirte函数部分做如下修改即可:
void *myread(void *arg)
{
sleep(1);//写者先运行
while(1)
{
if(pthread_rwlock_tryrdlock(&rwlock)!=0)
{
printf("reader say:the wirter is writing...\n");
usleep(500000);
}
else
{
printf("read done...%d\n",book);
pthread_rwlock_unlock(&rwlock);
}
}
}
void *mywirte(void *arg)
{
while(1)
{
if(pthread_rwlock_trywrlock(&rwlock)!=0)
{
printf("writer say:the reader is reading...\n");
}
else
{
book++;
printf("write done...%d\n",book);
sleep(5);
pthread_rwlock_unlock(&rwlock);
}
}
}
程序成功后结果如图所示: