读者写者问题—内含408真题

news2024/12/25 3:36:39

读者写者问题—含408

一、问题描述

一个数据问价或记录可以被多个进程共享,我们把只读该文件的进程称为“读者进程”,其他进程为“写者进程”。允许多个进程同时读一个共享对象,但不允许一个写者进程和其他写者进程或读者进程同时访问共享对象。即:保证一个写者进程必须与其他进程互斥的访问共享对象的同步问题;读者-写者问题常用来测试新同步原语。

二、解题思路

首先对于上述问题进行抽象:读者和写者是互斥的,写者和写者是互斥的,读者和读者不互斥;两类进程,一种是写者,另一种是读者。写者很好实现,因为它和其他任何进程都是互斥的,因此对每一个写者进程都给一个互斥信号量的P、V操作即可;而读者进程的问题就较为复杂,它与写者进程是互斥的,而又与其他的读者进程是同步的,因此不能简单的利用P、V操作解决。
下面我们给出三种方案来解决读者和写者之间、读者和读者之间、写者和写者之间的同步与互斥问题:

2.1 读者优先算法(一般意义上的读者写者问题)

为实现Reader和Writer进程之间在读或写时的互斥而设置了一个互斥信号量wmutex。再设置一个整型变量conut表示正在读的进程数目。
仅当count=0时,Reader进程才需要执行P(wmutex)操作;
仅当Reader进程在执行了count减一操作后其值为0时,才需要执行V(wmutex)操作
由于count是一个可被多个Reader进程访问的临界资源,因此为其设置一个互斥信号量cmutex;
其伪码描述如下:

semaphore cmutex=1,wmutex=1;
int count=0;

void Reader(){
	while(1){
		P(cmutex);
		if(count==0)
			P(wmutex);
		count++;
		V(cmutex);
		read;
		P(cmutex);
		count--;
		if(count==0)
			V(wmutex);
		V(cmutex);
	}
}

void Writer(){
	while(1){
		P(wmutex);
		write;
		V(wmutex);
	}
}

等待Reader进程全部结束之后才逐步执行Writer进程。我们称这样的算法为读者优先算法,也就是说,当存在读进程时,写操作将被延迟,并且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式下,会导致写进程可能长时间等待,且存在写进程“饿死”的情况。


2.2 写者优先算法

所谓写者优先,即:当有读者进程正在执行,写者进程发出申请,这时应该拒绝其他读者进程的请求,等待当前读者进程结束后立即执行写者进程,只有在无写者进程执行的情况下才能够允许读者进程再次运行。
为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

  • 在读者优先的基础上增加信号量rmutex,初值是1,用于禁止所有的读进程。
  • 增加一个记数器,即整型变量writecount,记录写者数,初值是0(原count改为readcount)。
  • writecount为多个写者共享的变量,是临界资源。用互斥信号量wmutex控制, wmutex初值是1
  • mutex用于实现互斥访问缓冲区
    伪码描述如下:
semaphore rmutex=1,cmutex=1,wmutex=1,mutex=1;
int readcount=0,writecount=0;
 
void Reader(){
	while(1){
		/**新增代码**/
		P(rmutex);// 取到该信号量说明此时并没有写进程在等待
		          // 后续读者才可以共享访问临界区
		          // 但是前面已经进入的读进程不受影响
		/**********/
		P(cmutex);
		if(readcount==0)
			P(mutex);
		readcount++;
		V(cmutex);
		/**新增代码**/
		V(rmutex);// 已经取到过这个信号量了
		          // 那么就说明已经得到了对临界区的读访问权限
		          // 此时可以一起读
		/**********/		
		read;
		V(cmutex);
		readcount--;
		if(readcount==0)
			V(mutex);
		V(cmutex);
	}
}
 
void Writer(){
	while(1){
		/**新增代码**/
		P(wmutex);
		writecount++;
		if(writecount==1)// 第一个写进程进来等待以后就
			P(rmutex);	 // 禁止读进程继续读了
		V(wmutex);
		/***********/
		P(mutex);
		write;
		V(mutex);
		/**新增代码**/
		P(wmutex);
		writecount--;
		if(writecount==0)//当没有写进程时,才允许读进程继续读
			V(rmutex);
		V(wmutex);
		/***********/
	}
}

2.3 读写公平

为实现读写公平,我们必须要同时满足以下四个条件:

  • 读者写者的优先级相同
  • 读者、写者互斥访问
  • 只允许有一个写者访问临界区
  • 可以有多个读者同时访问临界区的资源
    为此,我们设置一个互斥信号量mutex,其作用是让每个Writer进程和Reader进程进行公平争夺
  • 当Reader争夺到了,那么不管他是不是第一个Reader,他都有权利进入读操作(但是在进行读操作之前,需要释放这个互斥信号量,供后续的Reader和Writer继续公平争夺)
  • 当Writer争夺到了,就等待之前的Reader全部执行完,就可以上处理机运行
semaphore cmutex=1,wmutex=1,mutex=1;
int count=0;

void Reader(){
	while(1){
		/**新增代码**/
		P(mutex);//争夺优先权
		/**********/
		P(cmutex);
		if(count==0)
			P(wmutex);
		count++;
		V(cmutex);
		/**新增代码**/
		V(mutex);
		/**********/
		read;
		P(cmutex);
		count--;
		if(count==0)
			V(wmutex);
		V(cmutex);
	}
}

void Writer(){
	while(1){
		/**新增代码**/
		P(mutex);//争夺优先权
		/**********/
		P(wmutex);
		write;
		V(wmutex);
		/**新增代码**/
		V(mutex);
		/**********/
	}
}

这个最主要的思路就是让每一次运行的进程都有公平竞争的权利
因为读者优先算法,当读者上处理机运行后,写者就丧失了竞争的权利,只有当读者全部读完才可以重新竞争,这很不公平

2.4 双读者问题(2017年408真题)

先说明一下,双读者问题实际上并不存在,只是针对这道题提出的概念,由于使用常规的读者写者算法会导致并发度不够,所以特地将真题单独作为一个问题考虑

在这里插入图片描述
口说无凭,不如使用对比来更直观地展现其区别吧
首先是使用读者写者问题来解决该问题的算法:

semaphore mutex_x=1;// 对x的互斥访问
semaphore mutex_y=1;// 对y的互斥访问
semaphore mutex_z=1;// 对z的互斥访问
semaphore mutex_count=1;// 对计数器的互斥访问
int count=0;//计数器

void thread1(){
	cnum w;
	P(mutex_count);
	if(count==0)
		P(mutex_y);
	count++;
	V(mutex_count);
	P(mutex_x);
	w = add(x,y);
	V(mutex_x);
	P(mutex_count);
	count--;
	if(count==0)
		V(mutex_y);
	V(mutex_count);
	
}

void thread2(){
	cnum w;
	P(mutex_count);
	if(count==0)
		P_(mutex_y);
	count++;
	V(mutex_count);
	P(mutex_z);
	w = add(y,z);
	V(mutex_z);
	P(mutex_count);
	count--;	
	if(count==0)
		V(mutex_y);
	V(mutex_count);	
}

void thread3(){
	cnum w;
	w.a = 1;
	w.b = 1;
	P(mutex_z);
	z = add(z,w);
	V(mutex_z);
	P(mutex_y);
	y = add(y,w);
	V(mutex_y);
}

乍一看好像没啥问题,但是有一个很重要的问题是,虽然对于y的访问,实现了读写互斥,同时读与读可以同时进行,但是count信号量限制了并发度,导致两个读操作并不能以最快的方式运行完
下面是标准答案:

semaphore mutex_x=1;// 对x的互斥访问
semaphore mutex_y1=1;// 对y的互斥访问
semaphore mutex_y2=1;// 对y对互斥访问
semaphore mutex_z=1;// 对z的互斥访问
void thread1(){
	cnum w;
	P(mutex_y1);
	P(mutex_x);
	w = add(x,y);
	V(mutex_x);
	V(mutex_y1);
}

void thread2(){
	cnum w;
	P(mutex_y2);
	P(mutex_z);
	w = add(y,z);
	V(mutex_z);
	V(mutex_y2);	
}

void thread3(){
	cnum w;
	w.a = 1;
	w.b = 1;
	P(mutex_z);
	z = add(z,w);
	V(mutex_z);
	P(mutex_y1);// 只有同时拥有互斥信号量
	P(mutex_y2);// 才算是获取了访问权限
	y = add(y,w);
	V(mutex_y1);
	V(mutex_y2);
}

上面的代码,可以大大提高并发度,因为1,2两个线程也可以保证在读y时是绝对并行的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1054267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElementUI结合Vue完成主页的CUD(增删改)表单验证

目录 一、CUD ( 1 ) CU讲述 ( 2 ) 编写 1. CU 2. 删除 二、验证 前端整合代码 : 一、CUD 以下的代码基于我博客中的代码进行续写 : 使用ElementUI结合Vue导航菜单和后台数据分页查询 ( 1 ) CU讲述 在CRUD操作中,CU代表创建(Create&#xff09…

通过Nginx配置域名映射到本地项目

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…

Spring的注解开发-Spring配置其它注解

Spring配置其它注解 Primary 拓展:Primary注解用于标注相同类型的Bean优先被使用权,Primary是Spring3.0引入的,与Componen(及其衍生的三个注解)t和Bean一起使用,标注该Bean的优先级更高,则在通…

【知识回顾】Java常用类库-Java Runtime

文章目录 一、快速入门1.1 Runtime 介绍1.2 常用方法1.2.1 基本方法1.2.2 行为控制类1.2.3 系统信息类1.2.4 exec1.2.5 其他方法1.2.6 注意事项 二、基本使用2.1 获取当前虚拟机信息2.2 操作系统进程2.3 Process对象 三、业务场景实战3.1 执行外部脚本3.2 动态加载类 四、小结 …

《数据结构、算法与应用C++语言描述》-栈的应用-迷宫老鼠问题

迷宫老鼠 问题描述 迷宫(如图 8-9 所示)是一个矩形区域,有一个入口和一个出口。迷宫内部包含不能穿越的墙壁或障碍物。这些障碍物沿着行和列放置,与迷宫的边界平行。迷宫的入口在左上角,出口在右下角。 假定用 nxm 的…

【Java每日一题】——第十六题:将数组元素逆序并遍历输出。(2023.09.30)

🕸️Hollow,各位小伙伴,今天我们要做的是第十五题。 🎯问题: 设有数组如下:int[] arr{11,34,47,19,5,87,63,88}; 测试结果如下: 🎯 答案: int a[]new int [10];Random …

《三国志》游戏的MySQL数据设计与管理

在任何成功的游戏背后,都有一个精心设计和管理的数据系统。这不仅决定了游戏的运行效率,还直接影响到玩家的游戏体验。 本文将深入探讨著名游戏《三国志》中的数据设计和管理。本文将讲解游戏中核心的数据元素、数据管理方法,以及开发团队在数据方面所做的工作。 文章目录 …

【C语言次列车ing】No.1站---C语言入门

文章目录 前言一、什么是C语言二、第一个C语言程序三、数据类型四、变量、常量五、字符串转义字符注释 前言 👧个人主页:小沈YO. 😚小编介绍:欢迎来到我的乱七八糟小星球🌝 📋专栏:C语言次列车i…

考研王道强化阶段(二轮复习)“算法题”备考打卡表 记录

问题&#xff1a;做408真题_2010_42题&#xff0c;即王道书 2.2.3_大题_10 思路&#xff1a; 回头补 代码&#xff1a; int moveL(SqlList &L,SqlList &S,int p) {// 健壮性表达if( L.len 0 ){return 0;}// 调用另外一个顺序表存储pos前面的元素for( int i0;i<p;…

【模型压缩】模型剪枝模块

模型剪枝模块 最基本的基于阈值策略 基于分布来选择阈值 假定权重是符合一个正太分布正态分布有68% 小于标准差 将标准差作为阈值卷积层的敏感度要比全连接层更大&#xff1a;导致有些层over-pruning 有些层 under-pruning 设置预期的稀疏率 权重值按照绝对值进行排序从最小…

字符串函数与内存函数讲解

文章目录 前言一、字符串函数1.求字符串长度strlen 2.长度不受限制的字符串函数(1)strcpy(2)strcat(3)strcmp 3.长度受限制的字符串函数(1)strncpy(2)strncat(3)strncmp 4.字符串查找(1)strstr(2)strtok 5.错误信息报告(1)strerror(2)perror 二、内存函数1.memcpy2.memmove3.me…

山西电力市场日前价格预测【2023-10-02】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-02&#xff09;山西电力市场全天平均日前电价为355.35元/MWh。其中&#xff0c;最高日前电价为521.18元/MWh&#xff0c;预计出现在18: 45。最低日前电价为309.36元/MWh&#xff0c;预计…

【C语言深入理解指针(2)】

1. 数组名的理解 在上⼀个博客我们在使⽤指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&#x…

Windows驱动反调试的一种手段

Windows驱动反调试的一种手段 今天要介绍的是eprocess的0xbc位置 0x0bc DebugPort : Ptr32 Void DebugPort是在用windowsapi调试方式时候所使用的数据结构指针&#xff0c;那么如果我们能够循环清空这个值的话&#xff0c;就可以做到大部分windows调试api都无法正确调试进程 …

机器学习算法基础--聚类问题的评价指标

1.聚类问题指标评价的意义 聚类算法是非监督学习最常用的一种方法&#xff0c;性能度量是衡量学习模型优劣的指标&#xff0c;也可作为优化学习模型的目标函数。聚类性能度量根据训练数据是否包含标记数据分为两类&#xff0c;一类是将聚类结果与标记数据进行比较&#xff0c;称…

【Axure】常见元件、常用交互效果

产品结构图 以微信为例&#xff0c;根据页面层级制作 动态面板 动态面板的作用&#xff1a;动态面板是一个可视区域&#xff0c;如果要把控件放进去&#xff0c;要全部放进去&#xff0c;放多少显示多少 文本框 隐藏边框方法&#xff1a;先拉一个矩形&#xff0c;去掉部分…

uniapp项目实践总结(二十五)苹果 ios 平台 APP 打包教程

导语:当你的应用程序开发完成后,在上架 ios 应用商店之前,需要进行打包操作,下面就简单介绍一下打包方法。 目录 准备工作注册账号生成证书打包配置准备工作 在打包之前,请保证你的 uniapp 应用程序编译到 ios 模拟器或者是真机调试基座环境下是可以正常运行的,苹果打包…

记录一次阿里云服务器ECS上启动的portainer无法访问的问题

如下图&#xff0c;在阿里云ECS服务器上安装并启动了portainer&#xff0c;但是在自己电脑上访问不了远程的portainer。 最后发现是要在网络安全组里开放9000端口号&#xff0c;具体操作如下&#xff1a; 在云服务器管理控制台点击左侧菜单中的网络与安全-安全组&#xff0c;然…

国庆day2---select实现服务器并发

select.c&#xff1a; #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr,"__%d__:",__LINE__);\perror(msg);\ }while(0)#define IP "192.168.1.3" #define PORT 8888int main(int argc, const char *argv[]) {//创建报式套接字socketi…

Java笔记五(数组)

目录 数组 数组声明创建 数组初始化的三种初始化类型&#xff1a; 静态初始化 动态初始化 数组的默认初始化 数组的四个基本特点 数组边界 数组的使用 示例一&#xff1a;计算所有的元素和以及查找最大元素 示例二&#xff1a;For-Each循环 示例三&#xff1a;数组作…