采用信号量机制实现消费者与生产者的线程同步_信号量概述

1.先讲进程同步与互斥

进程互斥(mutex):进入临界区的进程只能有一个,当然前提是这些进程有共同的临界区。

进程同步:有逻辑关联的进程先后执行,比如B只有等A执行完了才能执行,A没执行完进程B只能挂起。这实际上是一种约束,更是一种通信。需要同步的进程之间不一定有共享临界区。

1)禁用硬件中断和基于硬件的原子操作(testandset以及exchange)单纯实现了进程互斥;

2)基于软件(peterson算法以及面包店算法)肯定实现了互斥,同时实现了同步;

3)信号量可以但不一定实现互斥(不是说不能,一种情况是不存在共享临界区,谈不上互斥,另一种情况是允许共同进入临界区,比如读操作),肯定实现了同步。

2.信号量

1)信号量(sem)是一个整型数字,程序开始需要给它一个初值,这里我们假设初值为org,其意义我们在后面根据信号量类型分开讨论。

sem=new Semaphore(org);

2)信号量有两个原子操作:P操作和V操作,具体意义也要分信号量类型的情况

·P() : sem减1

·V() : sem加1

3.信号量类型

1)二进制信号量

此时信号量的org初值只能是0和1。

2)一般/计数信号量

此时信号量的org初值可以是任意非负数。显然,其包含二进制信号量。

以下仅谈初值:

初值org设为0:此时信号量的意义单独拿出来,是因为此时信号量开始时可以有一个特殊的用途和分析。此时二进制信号量可以用于实现调度约束。只有线程B用V()发出信号(sem从0变到1)时,线程A中P()后的代码才能执行。
这是一个极简的进程同步例子,不牵扯互斥。当然单独实现这种操作不一定用信号量(信号量本质上还是一个类),随便定义变量赋任何值都可以对没有共享临界区的进程实现相同功能,只是信号量为0时恰好可以实现这种极简同步罢了。除了这一点外和其他信号量没有什么不同。

d1efcc89f57ad613ab50fe9fd44332cd.png

初值org设为1:此时信号量的值和一般信号量的值意义相同,即有多少个进程被允许访问临界区:同时只允许一个进程访问临界区——很明显的互斥概念。此时存在临界区,并且此时信号量就是一把“互斥锁”,和屏蔽中断、TestAndSet原子操作、peterson算法实现的功能完全一样,和原子操作尤其类似,因为PV本身也是原子操作(集判断能否进入和改变标志位为一身,不可被打断)。
但信号量自有的挂起,唤醒机制使其不存在忙等状态。

2a762b4bd8cdd1a81f04bdcfdcf2bf5d.png

cce981df8115efc4b7733d951a28572b.png

其他非负初值。表示有多少个进程可进入临界区,或者一个区域内有多少个资源。

4.信号量意义

PV过程中信号量大于0和小于0时,其代表的意义是不同的,这正是信号量的精妙所在。

当信号量≥0时,其表示还有sem个进程被允许进入临界区(某区域中还有sem个资源可以被获得),此时在临界区中的进程数目为org-sem(被取走的资源数目)。

当信号量<0时,表示有 |信号量| 个进程之前请求进入但不被允许后挂起的进程数目(想获得资源但目前给不了的进程数目),此时临界区中的进程数目为org(被拿走的资源数目),总的已经进入和即将进入的进程数目为org+|sem|(资源的总需求数目)。

允许多个线程进入的临界区限流?和缓存区资源的获取是信号量应用的两个主要方面。

5.例子:有界缓冲区的生产者消费者问题

30e69e4a8972907521089de8aa833c4a.png

1ae5631d3d35c0a3ce33dd395dc1e9a5.png

lock只能完成互斥要求,难以完成同步约束。消费者取不到东西会形成自旋,此时使用信号量主要使用其调度功能(何时挂起?何时唤醒?)。

d6ea8af354b7a8a763cc80ff1b7e89eb.png

V操作不会引起阻塞挂起,因此顺序可换,P操作会引起阻塞挂起,调换顺序可能引起死锁。

6.信号量实现

79a189c7ca105172753f93c65a7230aa.png

PV操作都是原子指令。

7.小结

信号量的双用途:互斥与同步

信号量的优点:无忙等

信号量这块本来就错综复杂,各种概念交织,总结成这样尽力了,就这样吧。。。。

参考:清华大学Chen Yu老师《操作系统原理》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验题目: 生产者消费者(综合性实验) 实验环境: C语言编译器 实验内容: ① 由用户指定要产生的进程及其类别,存入进入就绪队列。    ② 调度程序从就绪队列中提取一个就绪进程运行。如果申请的资源被阻塞则进入相应的等待队列,调度程序调度就绪队列中的下一个进程。进程运行结束时,会检查对应的等待队列,激活队列中的进程进入就绪队列。运行结束的进程进入over链表。重复这一过程直至就绪队列为空。    ③ 程序询问是否要继续?如果要转直①开始执行,否则退出程序。 实验目的: 通过实验模拟生产者消费者之间关系,了解并掌握他们之间关系及其原理。由此增加对进程同步的问题的了解。 实验要求: 每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程类型标号、进程系统号、进程状态、进程产品(字符)、进程链指针等等。 系统开辟了一个缓冲区,大小由buffersize指定。 程序中有三个链队列,一个链表。一个就绪队列(ready),两个等待队列:生产者等待队列(producer);消费者队列(consumer)。一个链表(over),用于收集已经运行结束的进程 本程序通过函数模拟信号量的操作。 参考书目: 1)徐甲同等编,计算机操作系统教程,西安电子科技大学出版社 2)Andrew S. Tanenbaum著,陈向群,马红兵译. 现代操作系统(第2版). 机械工业出版社 3)Abranham Silberschatz, Peter Baer Galvin, Greg Gagne著. 郑扣根译. 操作系统概念(第2版). 高等教育出版社 4)张尧学编著. 计算机操作系统教程(第2版)习题解答与实验指导. 清华大学出版社 实验报告要求: (1) 每位同学交一份电子版本的实验报告,上传到202.204.125.21服务器中。 (2) 文件名格式为班级、学号加上个人姓名,例如: 电子04-1-040824101**.doc   表示电子04-1班学号为040824101号的**同学的实验报告。 (3) 实验报告内容的开始处要列出实验的目的,实验环境、实验内容等的说明,报告中要附上程序代码,并对实验过程进行说明。 基本数据结构: PCB* readyhead=NULL, * readytail=NULL; // 就绪队列 PCB* consumerhead=NULL, * consumertail=NULL; // 消费者队列 PCB* producerhead=NULL, * producertail=NULL; // 生产者队列 over=(PCB*)malloc(sizeof(PCB)); // over链表 int productnum=0; //产品数量 int full=0, empty=buffersize; // semaphore char buffer[buffersize]; // 缓冲区 int bufferpoint=0; // 缓冲区指针 struct pcb { /* 定义进程控制块PCB */ int flag; // flag=1 denote producer; flag=2 denote consumer; int numlabel; char product; char state; struct pcb * processlink; …… }; processproc( )--- 给PCB分配内存。产生相应的的进程:输入1为生产者进程;输入2为消费者进程,并把这些进程放入就绪队列中。 waitempty( )--- 如果缓冲区满,该进程进入生产者等待队列;linkqueue(exe,&producertail); // 把就绪队列里的进程放入生产者队列的尾部 void signalempty() bool waitfull() void signalfull() void producerrun() void comsuerrun() void main() { processproc(); element=hasElement(readyhead); while(element){ exe=getq(readyhead,&readytail); printf("进程%d申请运行,它是一个",exe->numlabel); exe->flag==1? printf("生产者\n"):printf("消费者\n"); if(exe->flag==1) producerrun();
生产者消费者问题算法实现》 设计思想 因为有多个缓冲区,所以生产者线程没有必要在生成新的数据之前等待最后一个数据被消费者线程处理完毕。同样,消费者线程并不一定每次只能处理一个数据。在多缓冲区机制下,线程之间不必互相等待形成死锁,因而提高了效率。   多个缓冲区就好像使用一条传送带替代托架,传送带上一次可以放多个产品。生产者在缓冲区尾加入数据,而消费者则在缓冲区头读取数据。当缓冲区满的时候,缓冲区就上锁并等待消费者线程读取数据;每一个生产或消费动作使得传送带向前移动一个单位,因而,消费者线程读取数据的顺序和数据产生顺序是相同的。 可以引入一个count计数器来表示已经被使用的缓冲区数量。用hNotEmptyEvent 和hNotFullEvent 来同步生产者消费者线程。每当生产者线程发现缓冲区满( count=BufferSize ),它就等待hNotEmptyEvent 事件。同样,当消费者线程发现缓冲区空,它就开始等待hNotEmptyEvent。生产者线程写入一个新的数据之后,就立刻发出hNotEmptyEvent 来唤醒正在等待的消费者线程;消费者线程在读取一个数据之后,就发出hNotFullEvent 来唤醒正在等待的生产者线程。 程序的设计思想大致为:设置一while循环,pi生产者访问临界区,得到权限访问缓冲区,如果缓冲区满的,则等待,直到缓冲区非满;访问互斥锁,当得到互斥锁且缓冲区非满时,跳出while循环,开始产生新数据,并把数据存放于Buffer缓冲区中,当数据存放结束则结束临界区;接着唤醒消费者线程;ci消费者访问临界区,得到权限访问缓冲区,如果缓冲区为空,没有可以处理的数据,则释放互斥锁且等待,直到缓冲区非空;当等到缓冲区非空时,跳出while循环;消费者获得数据,并根据所获得的数据按类别消费(当消费者获得的数据为大写字母时,则把大写字母转换成小写字母,并显示;当消费者获得的数据为小写字母时,则把小写字母转换成大写字母,并显示;当消费者获得的数据为字符0、1、2、……8、9时,把这些字符直接显示到屏幕;当消费者获得的数据为符号(+、-、*、\……)时,把这些符号打印成7行7列的菱形);处理完数据后,结束临界区;接着唤醒生产者线程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值