一、概述
信号量是操作系统提供的一种协调共享资源访问的方法。和用软件实现的同步比较,软件同步是平等线程间的的一种同步协商机制,不能保证原子性。而信号量则由操作系统进行管理,地位高于进程,操作系统保证信号量的原子性。
信号量是跟锁机制在同一个层次上的编程方法。
管程是为了解决信号量在临界区的PV操作上的配对的麻烦,把配对的PV操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。
二、信号量
1)概述
信号中包括一个整形变量,和两个原子操作P和V,其原子性由操作系统保证,这个整形变量只能通过P操作和V操作改变。
P意味着信号量值减1,减完之后如果信号量值小于0,则说明资源不够用的,把进程加入等待队列。
V意味着信号量值加1,加完之后如果信号量值小于等于0,则说明等待队列里有进程,那么唤醒一个等待进程。
信号量的等待进程被放在等待队列中,按先进先出的次序执行。
自旋锁不能保证进程按先进先出的次序执行,因为自旋锁的所有等待进程都在循环中忙等待,在临界区释放的那一刻,先检查到临界区为空的进程先进入临界区,没有先来后到之分。
2)具体实现:
3)信号量分类:
i)二进制信号量:资源数目为0或1
ii)资源信号量:资源数目为任何非负值
两者其实是等价的,基于一个可以实现另一个。
4) 信号量作用:
i)实现临界区的互斥访问
进入临界区之前使用P操作,如果信号量为1,则进入,且信号量设置为0。如果信号量为0,则进入等待队列。
退出临界区之后使用V操作,信号量值加1,如果信号量还小于等于0,则唤醒等待队列中的一个进程。
ii)实现条件同步
一个线程A使用P操作,一个线程B使用V操作,初始的信号量设置为0。则为了满足某个条件,必须在线程B执行之后,才可以执行线程A。用信号量可以轻松实现这一点。
5)用信号量实现生产者-消费者问题
mutex和conditon的PV千万不要交换位置,会死锁!6)使用信号量的缺陷
读/开发代码比较困难,而且PV在不同的线程里配对,容易写错。而且必须先检查资源信号量的值,再进入临界区(即先写emptyBuffers->P(),再写mutex->P()),否则所有线程都不能进入临界区。
三、管程
1)概述
管程是为了解决信号量在临界区的PV操作上的配对的麻烦,把配对的PV操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。
管程与临界区不同的是,在管程中的线程可以临时放弃管程的互斥访问,让其他线程进入到管程中来。而临界区中的线程只能在线程退出临界区时,才可以放弃对临界区的访问。
2)管程的组成
当条件变量的数目为0时,管程和临界区相同。
3)条件变量
一个条件变量就对应于一个等待队列,每个条件变量有一个Wait()操作和Signal()操作。
4)用管程实现生产者-消费者问题
先进入管程,再进行判断。就是因为管程中的线程可以放弃对管程的互斥访问,交由其他线程访问管程。
(视频里说 管程可以把PV操作都集中到一个模块里,我对此有疑问。。在我看来,不就是把两个信号量表示成两个条件变量了嘛。。PV还是分散的啊。求指教)
5)管程条件变量的释放处理方式
进程A执行,被阻塞,进入等待队列,切换到进程B,当进程B执行signal操作使进程A可以执行时,有两种方式:一种是等进程B执行完再执行进程A,另一种是立即切换到进程A,等执行完进程A再切换到进程B,执行进程B。
第二种比第一种多了一次切换,所以第一种比较高效,但第二种容易证明其正确性。第一种主要用于真实OS和Java中,第二种主要见于教材中。
四、哲学家就餐问题
方案1:都先拿左边的叉子,再拿右边的叉子,拿不到就等着。当5个哲学家同时拿起左边的叉子时,就会引起死锁。
方案2:某一时间内,只能有一个哲学家在进餐,其他的人都不许拿叉子。结果正确,但是效率低。
方案3:按某一规律(下图是按奇偶数分类)让不同的哲学家先拿不同方向的叉子,即不是都先拿左手的叉子或右手的叉子,而是有的先拿左手的叉子,有的先拿右手的叉子。从而避免方案1中死锁的发生。
五、读者写者问题
网友评论