操作系统实验-进程同步与互斥

该文描述了一个使用C++实现的生产者消费者问题模型,通过定义仓库结构体,包含初始化、生产者、消费者等模块。利用互斥锁和条件变量解决并发访问冲突,确保缓冲区的满和空状态正确同步。在main函数中创建和管理生产者和消费者线程,实现动态输入数量并控制生产消费过程。
摘要由CSDN通过智能技术生成

程序由C++语言编写,通过定义warehouse结构体实现对仓库的抽象

包含六个函数模块Initwarehouse,Producer,Consumer,ConsumerMotion以及ProducerMotion,以及main函数模块。

其中,Initwarehouse用于初始化仓库warehouse,Producer和Consumer用于描述生产者和消费者,ConsumerMotion和ProducerMotion用于描述生产者和消费者的动作。

仓库结构体包含缓冲区大小,生产者和消费者生产和消费的位置,以及互斥锁和同步信号量。

main函数模块接受程序所必要的输入以及调度安排生产者和消费者的行动顺序,进行相关进程的初始化和回收工作。

2.3详细设计

结构体信息:

结构体warehouse包含buf(int数组)做为仓库容量(缓冲区),通过循环队列的方式访问。以及read_pos,write_pos分别作为消费者读取产品,生产者写入产品的位置。

包含四个互斥锁std::mutex mtx ,对仓库访问进行互斥,stdmtx,对输出进行互斥。pro_mtx生产者互斥生产,con_mtx用于消费者互斥消费。以及用于指示产品缓冲区不满和产品缓冲区不空的两个条件变量std::condition_variable not_full,not_empty。

模块Initwarehouse:

该模块只进行对结构体的初始化工作,初始化产品写入位置,产品读取位置为0,初始化生产者生产的产品序号,消费者消费的产品序号为0,该函数返回空。

模块Producer:

该模块被ProducerMotion调用,用于完成ProducerMotion模块中生产者共同的工作。模块接收仓库指针*wr和产品序号item,以及用于标识当前进程序号的num,然后进行如下操作。

  1. :获得对仓库互斥访问的锁mtx。

  1. :通过设置循环判断(wr->write_pos+ 1) % bufSize) == wr->read_pos这一条件,判断生产者放入的位置是否与消费者消费的位置相同,若相同,则通过not_full阻塞该生产者,表示等待"产品库缓冲区为空"这一条件发生.

  1. :可以写入产品,则写入并write_pos++,表示写入位置后移.并判断写入位置,若是在队列最后则重新设置为初始位置.

  1. :对同步信号量not_empty执行notify_all,唤醒全部消费者进行消费。

模块ProducerMotion:

该模块的动作在while循环中进行,通过设置bool值ready_to_exit判断当前线程是否需要退出循环。

  1. :sleep一秒,并获得生产互斥锁pro_mtx。

  1. :判断生产者生产总数是否大于所需,并判断该生产者生产是否小 于所能生产个数。若大于,设置ready_to_exit为true,准备退出。

  1. :若小于,则调用producr模块进行生产,生产完后输出必要信息并退出,输出消息需要在锁stdmtx下进行。

模块Consumer:

该模块被ConsumerMotion调用,用于完成ConsumerMotion模块中消费者共同的工作。模块接收仓库指针*wr和用于标识当前进程序号的num,最后返回所读取的数值data,具体操作如下。

  1. :获得对仓库互斥访问的锁mtx。

  1. :在while循环中判断write_pos是否等于read_pos,若等于,则通过not_empty信号量阻塞该消费者进程,表示消费者等待"产品库缓冲区不为空"这一条件发生。

  1. :读取缓冲区buf中的信息,并判断是否读到缓冲区末尾,若是则重新置read_pos为0。

  1. :对not_full信号量执行notify_all,通知生产者产品库不为满并返回data给模块ConsumerMotion。

模块ConsumerMotion:

该模块的动作在while循环中进行,通过设置bool值ready_to_exit判断当前线程是否需要退出循环。

  1. :sleep一秒,并获得消费者互斥锁con_mtx。

  1. :判断当前消费者消费的产品con_item_counter是否小于所有,若是,则通过调用consumer模块获得消费产品的序号,并进行必要的信息输出。

  1. :若否,则设置bool值ready_to_exit为false,并退出。

模块main函数:

该模块主要用于进行输入处理,以及创建进程和进程的退出处理,主要步骤如下:

  1. :调用Initwarehouse创建仓库实例。

②:读入生产者个数,消费者个数以及缓冲区容量,分别创建std::thread 类型的prothreads数组和conthreads数组,数组大小分别为m和n,对应生产消费者的个数。

  1. :循环读入生产者可消费的产品个数,存入数组product中。

④:通过调用std::thread(ConsumerMotion,i+1)和std::thread(ProducerMotion,i+1)创建生产者消费者进程。

  1. :调用thread.join等待被创建生产者消费者线程的结束,并回收它们的资源。

2.4调试分析

分析一:C还是C++

C++可以使用std::thread()创建进程,括号内传递一个函数,表示该进程执行的动作,在本次实验中,消费者和生产者的动作序列分别由函数ConsumerMotion()和ProducerMotion()描述。

需注意std::unique_lock对象以独占所有权的方式(unique owership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁,

分析二:动态输入消费者和生产者数量

对于动态输入消费者和生产者的数量,比较容易想到用对象数组实现,通过查询可知,可以通过创建线程数组prothreads[]和conthreads[],读入数量m和n后,循环对数组进行赋值,赋值方式为prothreads[i]= std::thread(ProducerMotion,i+1);在thread的构造函数中,第一个参数为线程的入口函数,后面为若干参数。

分析三:简化线程名字

对于一个线程,可以使用std::this_thread::get_id()获得其tid,然而tid的的表示不易于观察试验结果,故考虑简化线程名字,使其可以按序号输出,id为thread类中一个自定义类型,不能强制转换为double或int,且id没有重载%符号。于是考虑创建线程id数组,在输出某线程id时,通过数组对比该线程的序号,然后输出序号即可。查询知在pthread.h头文件中有函数int pthread_equal(pthread_t t1,pthread_tt2)可以比较id类型的数据,可是在实现过程中,该方法实现比较复杂,联想到thread构造函数可以传递自定义参数,即对于语句conthreads[i] =std::thread(ConsumerMotion,i+1);其中i+1表示该线程的顺序,可以实现在该线程执行对应动作时,输出对应序号。

分析四:cout乱序输出

该问题是,在输出中一行中既出现了生产者也出现了消费者,最初考虑到终端对于中文字符的支持不够完善,以为是linux终端多线程输出中文字符乱码的问题,改为英文字符输出验证该想法,却发现依然存在该问题,

考虑到cout是输出流,可能存在这样一种可能,虽然对消费者生产和生产者生产消费动作加锁,但是在某一次的输出时,二者的输出流在cout中没有刷新,导致输出混乱。考虑到在多线程环境中,刷新输出流可能也无法解决问题,依然会被抢占,故考虑加入输出锁std::mutex stdmtx,进行互斥输出,该问题得以解决。

代码如下:

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值