操作系统 | 信号量 & 管程

时间:2024-04-03 21:29:51

背景

操作系统 | 信号量 & 管程
需要更高层的同步功能,借用硬件资源
操作系统 | 信号量 & 管程
实现锁机制,对临界区操作,并且保证临界区操作是互斥的

信号量

进入临界后,可能不仅仅是写操作,还会有读操作,读操作可以不用去限制一个进程去执行,有没有更高级的手段实现

可以使用信号实现这种机制
操作系统 | 信号量 & 管程
P操作类似于上节课的获取lock操作,如果说在这里被挡住,则无法进入后续的操作
V操作是P操作的反操作,sem+1,唤醒一个挂在V操作上的一个P
操作系统 | 信号量 & 管程
之前的Lock机制只允许有一个进程执行,在信号量中多个
操作系统 | 信号量 & 管程

信号量使用

操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
对信号量做初始值,临界前P操作,结束后V操作
操作系统 | 信号量 & 管程
同步操作,这里储值的赋值要设置成0,这里有两个线程,线程A必须要等线程B执行到某个位置后再执行A,这是同步执行,使用信号量来实现
操作系统 | 信号量 & 管程
更复杂的情况,使用同步二值信号量不方便实现

现在有一个buffer,生产者向buff写数据,消费者线程从buff取出数据,这是一个双方需要用到同步和互斥的过程
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
设置三个信号量,其中一个二进制信号量用于互斥,还有两个计数信号量用于同步操作
操作系统 | 信号量 & 管程
mutex信号量,因为要互斥,赋值为1,保证第一个执行后,第二个不会执行,直到释放了(做了V操作之后)
fullBuffers赋值为0,即一开始是空的
emptyBuffers代表当前生产者可以向buff写入多少数据

设置完建立生产者和消费者的操作,生产者Deposit不停的添加数据,消费者Remove是不停的取数据
操作系统 | 信号量 & 管程
这里要保证buff的互斥,通过P操作V操作来保证,用P V 包起来
操作系统 | 信号量 & 管程
通知满和没满怎么实现,对于生产者,buff没有满就可以继续执行,使用emptyBuffer这个信号量,首先执行emptyBuffers的P操作,emptyBuffers初始值为n,意味着表示有n个生产者可以执行Deposit,一直到emptyBuffers小于0才会被阻塞,添加完数据后

执行fullBuffers的V操作,因为fullBuffers的赋值为0,意味着一开始消费者想从buff取数据是取不到的,因为是0,通过生产者执行fullBuffers操作,告诉消费者buff里面有了数据,这里V操作+1

消费者是一个相反的过程,如果说生产者先执行,我们的fullBuffers赋值为1,接下来消费者执行P操作

如果消费者先执行,因为fullBuffers的初始值为0,接下来的P操作会被挡住,直到生产者执行后填了数据,才能继续执行

如果生产者很快填满了buff,这里在emptyBuffers的P操作会被挡住,需要消费者执行emptyBuffers的V操作+1,从而使得当前这个buff不满的情况通知到生产者睡眠在emptyBuffers的进程继续执行

信号量实现

操作系统 | 信号量 & 管程
对于操作系统 P操作 V操作的实现

信号量本身是整形,所以需要一个整形来记录P操作V操作+或者-的具体值

P操作中有可能实现有进程等在信号量上面,等操作怎么实现?

结合之前进程管理情况下,当进程执行到某种情况下,进程的某种资源得不到满足会进入sleep,这时候可以把信号量看成是虚拟的资源,执行P操作时候,由于条件得不到满足,所以值是小于0,这种情况下,让进程睡在信号量上,这时候我们需要一个等待队列,这个队列实际上是一系列的等待信号量大于等于0的情况下,等待信号量在执行V操作后要去唤醒的过程
操作系统 | 信号量 & 管程
P操作中,sem做出-1的操作,接下来判断,如果信号量小于0,把线程放到等待队列中,同时自身睡眠
操作系统 | 信号量 & 管程
V操作首先是sem+1的操作,然后进一步判断是否有线程睡眠在这个信号量上面,这里怎么判断呢?
信号量小于等于0,意味着之前的信号量的值是小于0,也就是和之前P操作的判断是一致的,这时候认为当前有进程睡眠在这个信号量上面,在等待队列中取出一个进程,最好采用先进先出的逻辑队列
操作系统 | 信号量 & 管程

管程

操作系统 | 信号量 & 管程
管程提出是从编程语言中,让高级语言满足同步互斥的操作,针对语言的并发机制完成
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
条件变量实现,跟信号量操作类似
操作系统 | 信号量 & 管程
这里用管程来解决上面的生产者消费者问题

count记录了buff当前的空闲状态,生产者Deposit,消费者Remove。

count buff是共享变量,所以要满足互斥,这里跟信号量互斥不一样,信号量是紧靠buffer,这里是头和尾,这是管程的定义决定,线程进入管程的时候只有一个能进去,确保进入管程的线程是唯一的互斥的。

buffe满了cont = n,执行notFull.Wait表示当前满了,睡眠。生产者睡眠前,需要做一个release的操作。
对于生产者,count–了,buff也就是不满了,需要做一个题型,也就是这里notFull.Signal,一旦notFull里有生产者的等待线程就被唤醒

buffer空的时候在消费者判断,notEmpty.Wait睡眠,直到生产者产生notEmpty.Signal通知
操作系统 | 信号量 & 管程
当线程在管程中执行时,如果某一个线程需要执行针对某一个条件变量的signal唤醒操作,完成后,马上执行等在条件变量的线程还是发出通知唤醒操作这个线程执行完毕之后再去让等待线程执行。

一旦发出signal操作,也就意味着有两个线程都可以执行,一个是本身发出signal信号的线程,另一个唤醒线程的本身。哪一个先执行?

Hoare的方法,一旦发出signal信号后,让等待的线程执行,本身去睡眠,直到等待线程执行完毕本身再去执行
Hansen的方法,发出signal信号之后并不一定要马上放弃控制权,而是说做发出signal信号的线程执行完release操作后再交出控制权
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程

经典同步问题

读者-写者问题——信号量实现

假设一段数据,读者读,写着写,要满足互斥,不能同时进行,也不允许多个写者操作,允许多个读者操作
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
当有读者读的时候,写者需要等待,如果写者需要写操作,读者也要等待。
操作系统 | 信号量 & 管程
读者优先,也就是读者操作时,写者需要等待,如果等待的写者后还有读者,等待的读者也是要优先

data是共享变量,Rcount获取共有多少的读者,写者只有一个可以进入数据集操作,读者多个,CountMutex保证Rcount可以进行互斥的操作,WeiteMutex写者的互斥保护。

写者,sem_wait相当于P操作,sem_post相当于V操作,包起来之后writer保证只有一个写者操作

读者,如何多个读者进入,首先有个判断Rcount=0,没有一个读者,也即是第一个读者,只要在确定没有写者,就可以进行操作了,要是Rcount不等于0,也就是有读者线程在读数据,接下来的操作写者一定无法进入,Rcount++然后完成度,执行完之后Rcount–,要是等于0了,也就是读完了没有读者了,可以允许写者进入了。Rcount要满足互斥,所以要包起来,不会存在多个读者线程都对Rcount进行++或者–操作。
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程

读者-写者问题——管程实现

操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程
操作系统 | 信号量 & 管程

哲学家就餐问题

操作系统 | 信号量 & 管程