2.4 进程同步
2.4.1 进程同步的基本概念
1. 两种形式的制约关系
(1)间接相互制约关系:互斥问题(往往是互斥设备)---是同步的特例
(2)直接相互制约关系:同步问题
注:
互斥问题:共享变量的修改冲突
同步问题:操作顺序冲突,先后关系
2. 临界资源
许多硬件资源如打印机、磁带机等,都属于临界资源,诸进程间应采取互斥方式,实现对这种资源的共享。
经典例题:生产者-消费者问题
有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品
生产者-消费者问题一解决思路
利用一个数组来表示上述的具有 n 个(0, 1, ... n-1)缓冲区的缓冲池。用输入指针 in 来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入指针加1;用一个输出指针 out 来指示下一个可从中获取产品的缓冲区,每当消费者进程取走一个产品后, 输出指针加1。设缓冲池的组织是循环缓冲,输入指针加1表示成 in=(in+ 1)mod n;输出指针加1表示成out=(out+1) mod n,循环队列(需要有判断队满和队空)
在操作系统中,引入一个整型变量 counter ,其初值为0。每当生产者进程向缓冲池投放一个产品,使 counter 加1;反之,每当消费者进程从中取走一个产品时,使 counter 减1。
注:操作系统代码与c语言代码有区别,上述代码死循环套死循环的伪代码要注意是正确的。
若并发执行,就会出现错误,问题在于两个进程共享变量 counter 。例如:生产者对它做加1操作,消费者对它做减 1 操作,可用以下机器语言实现,形式描述如下:
3. 临界区(critical section)
由前所述可知,不论是硬件临界资源还是软件临界资源,多个进程必须互斥地对它进行访问。人们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。每个进程进入临界区之前应先对欲访问的临界资源进行检查,看是否正在被访问。如果此刻该临界资源未被访问,该进程可进入临界区,并设置它正在被访问的标志,在临界区之前执行的这段代码称为进入区(entry section)在临界区之后也要加上一段代码,用于将临界区被访问的标志恢复为未被访问的标志,称为退出区(exit section)
4. 同步机制应遵循的规则(重点背)
-
空闲让进
-
忙则等待
-
有限等待
-
让权等待
注:有限等待不能死等;让权(让的是CPU)不能忙等,自己进入阻塞状态(block)中。
2.4.2 软件同步机制(408考过一次)
1. 单标志法
优点:实现互斥
缺点:turn 成为了制约两个进程的变量,资源浪费
2. 双标志法先检查(设计 flag [ 2 ] )
优点:可以实现互斥同步;
缺点:容易同时访问临界区
3. 双标志法后检查
优点:可以实现互斥同步;
缺点:会造成死锁,while循环出不来
4. Peterson 算法(背下来)
综合单标记法和双标记法(三个标记),有turn表示谦让,flag[2]表示想访问,中心还是谁手里面有钥匙。
2.4.2 硬件同步机制
用特殊的指令来实现
1. 关中断
关中断是实现互斥的最简单的方法之一。 在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样,进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会发生进程或线程切换。由此,保证了对锁的测试和关锁操作的连续性和完整性,有效地保证了互斥。
缺点:
-
滥用关中断权力可能导致严重后果;
-
关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力;
-
关中断方法也不适用于多CPU系统,因为在一个处理器上关中断并不能防止进程在其它处理器上执行相同的临界段代码。
2. 利用 Test - and -Set 指令实现互斥
这是一种借助一条硬件指令——“测试并建立”指令 TS 一实现互斥的方法
3. 利用 Swap 指令实现进程互斥
指令称为对换指令,在 Intel 80x86 中有称为 XCHG 指令,用于交换两个字的内容。
2.4.3 信号量机制
前言:
整型信号量和记录型信号量的进程互斥问题针对是多个并发进程仅共享一个临界资源的情况。
AND型信号量是一个进程往往需要获得两个或更多的共享资源后方能执行其任务。
1. 整型信号量
最初由Dijkstra(整最短路径算法的人)把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。这两个操作一直被分别称为P、 V操作。
wait 和 signal操作描述:
wait( S ) { while (S<=0);/* do no-op */ S=S-1; } signal( S ) { S=S+1; }
wait(S) 和 signal(S) 是原子操作,执行时是不可中断的。另外,在wait操作中,对S的测试和做S=S-1操作时都不可中断,信号量只能通过原语操作来访问,不能被进程调度所打断
缺点:信号量‘S<=0’时“忙等”,未遵循 “让权等待”
2. 记录型信号量
记录型信号量机制是一种不存在“忙等”现象的进程同步机制。
在采取 “让权等待” 的策略后,又会出现多个进程等待访问同一临界资源的情况.。在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程
typedef struct { int value; struct PCB *list; //每一个块里面放的都是PCB,且进程堵塞原因一致 }semaphore;
相应的 wait(S) 和 signal(S) 操作为:
wait(semaphore *S) { S->value--; //请求一个该类资源 if (S->value<0) block(S->list); //该类资源已分配完毕,调用block原语,进行自我阻塞并放弃处理机、插入到信号量链表S.L中 } signal(semaphore *S) { S->value ++; // 已经释放进程 if(S.value<=0) wakeup(S->list); // 相应的可以唤醒一个进程 }
注:
(1)S.value 的值为负数的绝对值为阻塞进程的个数,正数为进程个数(该类资源数)。
(2)S.value 的初值表示系统中某类资源的数目,又称资源信号量
(3)若 S.value 的初值为1,表示只允许一个进程访问临界资源,此时信号量转化为互斥信号量
3. AND型信号量
上述例题会造成死锁现象,故引进AND同步。在 wait 操作中,增加一个 "AND" 条件,成AND同步,或称同时 wait 操作,Swait 。(自主命题考过,408也没准目前没有)
将进程在整个运行过程中需要的所有资源,一次性全部的分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源,也不分配给他。对若干个临界资源的分配,采取原子操作方式:要么全部分配到进程,要么一个也不分配。
Swait (S1,S2,...,Sn) { while(TRUE) { if(Si >= 1 && ...&& Sn>=1) //每个资源都可用 { for(i=1;i<n;i++) Si -- ; //分配所有资源 break; } else { //否则,将进程放到等待资源 Si 的队列中 place the process in the waiting queue associates with the first Si found with Si < 1,and set the program count of this process to the beginning of Swait operation} } } Ssignal(S1,S2,...,Sn) { while(TRUE) { for (i=1;i<n;i++) { Si ++; //释放所有资源 Remove all the process waiting in the queue associsted with Si into the ready queue.} } }
4. 信号量集(了解熟悉,考的概率较低)
为确保系统的安全性,当所申请的资源数量低于某一下限值时,还必须进行管制,不予以分配。因此,当进程申请某类临界资源的时,在每次分配之前都必须测试资源得的数量,判断是否大于可分配的下限值,决定是否予以分配。
注:
(1)下限值 ti , 需求值 di
(2)Si >= ti 进行分配,否则不予分配。
(3)wait 操作修改为Si = Si -di , 在对AND型信号量机制扩充的基础上,形成一般化的 “信号量集” 机制
- Swait (S1, t1 , d1,...,Sn, tn , dn)
- Ssignal(S1, d1 ,...,Sn , dn)
下图为三种特例: