1、进程同步的背景
有一个环形缓冲池,包含n个缓冲区(0~n-1)(如下图所示:)。有两类进程:一组生产者进程和一组消费者进程,生产者进程向空的缓冲区中放产品,消费者进程从满的缓冲区中取走产品。
2、生产者进程:
while (true) { /* 生产一个产品*/ while (count == BUFFER_SIZE) ; // do nothing buffer [in] = nextProduced; in = (in + 1) % BUFFER_SIZE; count++; }
3、消费者进程:
while (true) { while (count == 0) ; // do nothing nextConsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--;
//count++ 的执行过程(机器指令): register1 = count register1 = register1 + 1 count = register1 //count-- 的执行过程(机器指令): register2 = count register2 = register2 - 1 count = register2
设初始状态为 “count = 5” :(按照时间片轮询(按照颜色区分),最终运行结果)
S0: producer execute register1 = count {register1 = 5}
S1: producer execute register1 = register1+1 {register1 = 6}
S2: consumer execute register2 = count {register2 = 5}
S3: consumer execute register2=register2-1 {register2 = 4}
S4: producer execute count = register1 {count = 6 }
S5: consumer execute count = register2 {count = 4}
正确结果应该是: “count = 5” , 现在的结果是: “count = 4”
在多道程序环境下,这里两道程序,进程并发执行,不同进程存在相互制约的关系。为了协调该关系,避免进程冲突,引入了进程同步。
4、进程同步的概念:
临界资源:一次仅允许一个进程使用的资源称为临界资源。比如:打印机、共享变量等。
临界区:是指并发进程中访问临界资源的程序段。
进入区:检查是否可以进入临界区,若可以,设置正在访问临界区标志。
退出区:清除正在访问临界区标志。
进程的互斥是指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其它要使用该资源的进程必须等待,直到占有资源的进程释放该资源。
进程的同步是解决进程间协作关系的手段。指一个进程的执行依赖于另一个进程的消息,当一个进程没有得到来自于另一个进程的消息时则等待,直到消息到达才被唤醒。
进程互斥关系是一种特殊的进程同步关系。
5、临界区访问原则:
互斥访问– 若已有进程进入临界区,则其他的进程必须等待其离开临界区,释放临界资源。 (忙则等待)
前进– 若没有进程处于其临界区,应允许一个请求进入临界区的进程立即进入临界区,访问临界资源。(空闲让进)
有限等待– 对请求访问的进程,应保证能在有限的时间内进入临界区,避免进入“死等”。(避免死锁问题)
让权等待– 当进程不能进入临界区时,该进程应释放处理机,以免进入“忙等”状态。
6、进程同步的实现方法-----软件实现
在进入区设置和检查一些标志来标明是否有进程在临界区,若有则在进入区循环检查进行等待,进程在退出区修改标志,以允许别的进程进入临界区。
Peterson 算法
1981年,由Peterson提出,满足临界区访问的4原则。 设有两个进程Pi和Pk,且 LOAD 和 STORE 指令是原子操作。 Pi和Pk共享两个变量:
int turn; Boolean flag[2] ;
变量 turn:
turn==i 表示Pi可进入其临界区。
数组 flag :
flag[i] = true 表示进程Pi请求进入临界区!
Pi进程:
while (true) { flag[i] = TRUE; //Pi想进入 turn = k; //Pk可以进入 while ( flag[k] && turn == k) ; CRITICAL SECTION flag[i] = FALSE; REMAINDER SECTION }
Pk进程:
while (true) { flag[ k] = TRUE; turn = i; while ( flag[i] && turn ==i) ; CRITICAL SECTION flag[k] = FALSE; REMAINDER SECTION }
若pi和pk同时请求进入临界区,while中的turn变量可保证只允许一个进入临界区,从而实现了互斥。考虑Pi进程的代码,flag[i] = true;意味着Pi想进入临界区,同时将turn设置为k, 若Pk在临界区,则Pi的while条件为真,Pi等待。若PK不在临界区,则flag[k]为false,Pi进入临界区,从而避免了死等。
7、进程同步的实现方法-----硬件方法
很多系统都提供了解决临界区问题的硬件支持。 对于单处理器环境 – “禁止中断” 并发进程可以无预设地运行 限制了交替执行程序的能力,执行效率明显降低。 许多现代计算机系统提供了特殊的原子(执行该代码时不允许被中断)机器指令:
TestAndSet指令:读出标志并把该标志设置为TRUE。
Swap指令:交换两个内存字的内容。
8、信号量
Dijkstra发明了两个信号量操作原语:P操作原语和V操作原语。常用的其他符号有:wait和signal;up和down等。 (原语是操作系统内核中执行时不可中断的过程,即原子操作) 除赋初值外,信号量仅能由同步原语对其进行操作,没有任何其他方法可以检查和操作信号量。 利用信号量和P、V操作既可以解决并发进程的竞争问题,又可以解决并发进程的协作问题。
整型信号量:
记录型信号量:
实现同步:
实现互斥:
前驱图:
semaphore a=0,b=0,c=0,d=0,e=0,f=0,g=0; //其他代码 { 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; } //其他代码
经典同步问题有:
生产者----消费者问题
读者--写者问题
哲学家进餐问题