1965年,由荷兰学者迪科斯彻Dijkstra提出(P、V分别代表荷兰语的Proberen (test)和Verhogen (increment))、是一种卓有成效的进程同步机制。
信号量-软件解决方案:
信号量的类型可分为:
用一个整数型的变量 "S"作为信号量,用来表示系统中某种资源的数量。
wait(S) : while S<=0; /*do no-op*/ S:=S-1; signal(S): S:=S+1;
- 提供两个不可分割的[原子操作]访问信号量
- Wait(s)又称为P(S)
- Signal(s)又称为V(S)
- 缺点:进程忙等?
- 为了解决“忙等”问题,而提出的信号量。
- 每个信号量S除一个整数值S.value外,还有一个进程等待队列S.list,存放阻塞在该信号量的各个进程PCB
- 信号量只能通过初始化和两个标准的原语PV来访问--作为OS核心代码执行,不受进程调度的打断
- 初始化指定一个非负整数值,表示空闲资源总数(又称为"资源信号量")-- 若为非负值表示当前的空闲资源数,若为负值其绝对值表示当前等待临界区的进程数
typedef struct { int value; stuct process_control_block *list; }semaphore;
相应地,wait(S)和signal(S)操作可描述为:
wait(semaphores *S) { //请求一个单位的资源 S->value --; //资源减少一个 if (S->value<0) block(S->list) //进程自我阻塞 } signal(semaphores *S){ //释放一个单位资源 S->value++; //资源增加一个 if (S->value<=0) wakeup(S->list); //唤醒等待队列中的一个进程 }
- AND型信号量同步的基本思想:将进程在整个运行过程中需要的所有资源,一次性全部分配给进程,待进程使用完后再一起释放。
- 对若干个临界资源的分配,采用原子操作。
- 在wait(S)操作中增加了一个 “AND” 条件,故称之为AND同步,或同时wait(S)操作,即Swait(Simultaneous wait)。
- 在记录型信号量中,wait或signal仅能对某类临界资源进行一个单位的申请和释放,当需要对N个单位进行操作时,需要N次wait/signal操作,效率低下
- 扩充AND信号量:对进程所申请的所有资源以及每类资源不同的资源需求量,在一次P、V原语操作中完成申请或释放
- 进程对信号量Si的测试值是该资源的分配下限值ti,即要求Si≥ti,否则不予分配。一旦允许分配,进程对该资源的需求值为di,即表示资源占用量,进行Si= Si-di操作
- Swait(S1, t1, d1, ..., Sn, tn, dn)
- Ssignal(S1, d1, ..., Sn, dn)
????????key:设置互斥信号量
????????为使多个进程能互斥地访问某临界资源,只须为该资源设置一个互斥型信号量mutex,并设其初值为1,然后将各进程访问该资源的临界区置于wait(mutex) 和signal(mutex)操作之间即可。
????????这样,每个欲访问该临界资源的进程,在进入临界区之前,都要先对mutex执行wait操作。若该资源此刻未被访问,则本次wait操作必然成功,进程便可进入自己的临界区,这时若再有其他进程也欲进入自己的临界区,则由于对mutex执行wait操作定会失败,因而该进程阻塞,从而保证了该临界资源能被互斥地访问。当访问临界资源的进程退出临界区后,又应对mutex执行signal操作,以便释放该临界资源。利用信号量实现两个进程互斥的描述如下。
- 设mutex为互斥型信号量,其初值为1,取值范围为(-1,0,1)。当mutex=1时,表示两个进程皆未进入需要互斥访问的临界区;当mutex=0时,表示有一个进程进入临界区运行,另一个必须等待,挂入阻塞队列;当mutex=-1时,表示有一个进程正在临界区运行,而另一个进程因等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程在退出时唤醒。
- 代码描述:
semaphore mutex=1; //初始化为1 PA(){ while (1){ wait (mutex); 临界区; signal (mutex); 剩余区; } } PB(){ while (1){ wait (mutex); 临界区; signal (mutex); 剩余区; } }
在利用信号量机制实现进程互斥时应注意,wait(mutex) 和 signal(mutex)必须成对出现。缺少wait(mutex)将会导致系统混乱,无法保证对临界资源的互斥访问;缺少signal(mutex)将会导致临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。
main (){ Semaphore a,b,c,d,e,f,g; a.value=0;b.value=0;c.value=0; d.value=0;e.value=0;f.value=0;g.value=0; cobegin { S1 ;signal(a);signal(b); } { wait(a);S2;signal(c);signal(d);} { wait(b);S3;signal(e); } { wait(c);S4;signal(f); } { wait(d);S5;signal(g); } { wait(e);wait(f);wait(g);S6; } corend }
????????key:设置同步信号量
????????假设进程P1和P2中有两段代码C1和C2,若要强制C1先于C2执行,则须在C2前添加wait(S),在C1后添加signal(S)。需要说明的是,信号量S的初值应该被设置为0。这样,只有P1在执行完C1后,才能执行signal(S)以把S的值设置为1。这时,P2执行wait(S)才能申请到信号量S,并执行C2。如果P1的C1没有提前执行,则信号量S的值为0,P2执行wait(S)时会因申请不到信号量S而阻塞。
- 设S为同步型信号量,其初值为0,取值范围为(-1,0,1)。当S=0时,表示C1还未执行,C2也未执行;当S=1时,表示C1已经执行,C2可以执行;当S=-1时,表示C2想执行,但由于C1尚未执行,C2不能执行,进程P2处于阻塞状态。
- 代码描述:
semaphore S=0; //初始化为0 P1() { while (1) { C1 signal(S); ... } } P2() { while (1) { wait(S); C2 ; ... } }
同步型信号量的使用通常比互斥型信号量的使用要复杂。一般情况下,同步型信号量的wait(S)和signal(S)操作位于两个不同的进程内。
1、请思考互斥与同步的关系,它们有哪些地方相同?哪些地方不同?
相同之处:
- 目的:互斥和同步都是为了协调多个进程对共享资源的访问,以避免竞争条件和数据不一致性。
- 都可以通过信号量、互斥锁等机制来实现。
不同之处:
- 定义:互斥是指在任意时刻只允许一个进程访问共享资源,而同步是指协调进程之间的交互和通信,确保它们按照一定顺序执行。
- 目标:互斥的目标是防止多个进程同时访问共享资源,而同步的目标是确保进程按照特定顺序执行,或者在某个条件满足时进行通信和协调。
- 实现方式:互斥通常使用互斥锁或信号量来实现,而同步可以使用信号量、条件变量、屏障等机制来实现。
2、桌上有一空盘,允许存放一只水果。爸爸可向盘中放苹果,妈妈可向盘中放香蕉,儿子专等吃盘中的香蕉,女儿专等吃盘中的苹果,请按照要求回答:
? ? ? ? (1)本题中存在哪些互斥关系和同步关系?
? ? ? ? (2)试用P、V操作描述放水果和吃水果的过程。
答: (1)在本题中,盘中一次只能放一个水果;妈妈和爸爸对盘子的使用存在互斥关系,所以需要设互斥信号量mutex;爸爸和女儿,妈妈和儿子都是同步关系,需设置同步信号量apple, banana
????????(2)设置信号量mutex, apple, banana,初值分别为1,0,0。分别表示可否向盘中放水果,可否取苹果,可否取香蕉。
代码段如下:???
Father(){?? while (true){ p(mutex); //申请盘子使用权 将苹果放入盘子; v(apple); //苹果产品+1 } } Mother(){? while (true){ p(mutex); //申请盘子使用权 将香蕉放入盘子; v(banana); //香蕉产品+1 } } son(){? while (true){ p(banana); //申请香蕉资源,若非空 从盘子取香蕉; v(mutex); //盘子空了,释放盘子使用权 吃香蕉; } } daugher(){? while (true){ p(apple); //申请苹果资源,若非空 从盘子取苹果; v(mutex); //盘子空了,释放盘子使用权 吃苹果; } }
3、公共汽车上有1名司机和1名售票员,司机和售票员活动如下,车辆运行规则是只有到站停车后售票员才能开门,只有关门后司机才能启动车辆。初始状态是车辆停在起始站。使用P、V原语描述车辆运行过程。
分析:本例中存在同步关系,即只有售票员关门后司机才能启动车辆;只有司机到站停车后售票员才能开门;
故设置:
????????司机的同步信号量: S1表示是否允许司机启动车辆,初值为0
????????售票员同步信号量: S2表示是否允许售票员开门,初值为1
则车辆运行过程描述如下:
driver: while (true){ P(S1);//申请启动车辆,若标志为1,则可以启动 启动车辆; 正常运行; 到站停车; V(S2); } conductor: while (true){ P(S2); //申请开门,若标志为1,则可以开门 开门; 售票; 关门; V(S1); ??? }