操作系统-进程(6)管程
管程试图抽象相关并发进程对共享变量访问,以提供一个友善的并发程序设计开发环境
管程是由若干公共变量及其说明和所有访问这些变量的过程所组成
管程把分散在各个进程中互斥地访问公共变量的那些临界区集中起来管理,管程的局部变量只能由该管程的过程存取
进程只能互斥地调用管程中的过程
TYPE <管程名> = MONITOR <管程变量说明>; define <(能被其他模块引用的)过程名列表>; use <(要引用的模块外定义的)过程名列表>; procedure <过程名>(<形式参数表>); begin <过程体>; end; …… procedure <过程名>(<形式参数表>); begin <过程体>; end; begin <管程的局部数据初始化语句>; end;
条件变量(condition variables):当调用管程过程的进程无法运行时,用于阻塞进程的信号量
阻塞原语wait:当一个管程过程发现无法继续时(如发现没有可用资源时),它在某些条件变量上执行wait,这个动作引起调用进程阻塞
释放原语signal:用于释放在条件变量上阻塞的进程
如图,管程过程必须被互斥调用,等待调用管程过程的进程队列必须在互斥调用管程过程队列排队
每个条件变量都对应一个条件变量队列。调用管程过程的进程执行不下去的时候,进入对应条件变量队列等待,并开放管程。
当使用signal释放一个等待进程时,可能出现两个进程同时停留在管程内。解决方法:
(1)执行signal的进程等待,直到被释放进程退出管程或等待另一个条件
(2)被释放进程等待,直到执行signal的进程退出管程或等待另一个条件
被释放的进程在互斥调用管程过程高优先级队列,等待调用管程过程。
霍尔采用了第一种办法;汉森选择了两者的折衷,规定管程过程所执行的signal操作是过程体的最后一个操作,但该方法增加了程序设计的难度
霍尔管程
霍尔管程基于PV操作原语实现:wait和signal可以是程序过程,不需要是原语
可以用语言机制(如通过操作系统程序库或高级程序设计语言在基础的操作系统的PV操作原语上)实现霍尔管程,而不需要扩展操作系统内核
用于管程中过程互斥调用的信号量mutex,初值为1
挂起发出signal()操作的进程的信号量next和计数器next_count
挂起等待资源的进程的信号量x_sem和计数器x_count(假设只有一个共享变量)
typedef struct InterfaceModule{ Semaphore mutex; Semaphore next; int next_count; } mutex = 1; next = 0; next_count = 0; procedure enter(InterfaceModule &IM) { P(IM.mutex); //互斥进入管程 } procedure leave(InterfaceModule &IM) { if(IM.next_count>0) { //开放管程时首先释放互斥调用管程过程高优先级队列里的进程,没有才释放低优先级队列中的 IM.next_count--; V(IM.next); //释放一个next上挂起的进程 } else V(IM.mutex);//否则向互斥调用管程过程队列开放管程 } procedure wait(semaphore &x_sem , int &x_count , InterfaceModule &IM) { x_count++; //等待这个资源的进程数加1 if(IM.next_count>0) { //首先释放高优先级队列里的进程 IM.next_count--; V(IM.next); } else V(IM.next); P(x_sem); } procedure signal(semaphore &x_sem , int &x_count , InterfaceModule &IM) { if(x_count>0) { //判断条件变量队列中是否有等待资源的进程 x_count--; IM.next_count++; V(x_sem); //释放一个等待资源的进程 P(IM.next); }
霍尔管程实现哲学家就餐问题:
当且仅当哲学家的两个邻座state[(i-1)%5]和state[(i+1)%5]均不为eating时才建立状态state[i]=eating
引入条件变量semaphore self[5],当哲学家i饥饿但又不能获得两把叉子时,进入其信号量等待队列
type dining philosophers = monitor { enum{think , hungry , eating} state[5]; cond self[5]; self[5] = 0; int self_count[5]; self count[5] = 0; InterfaceModule IM; for(int i=0; i<5; i++) state[i] = thinking; define pickup , pickdown; use enter , leave , wait , signal; } procedure pickup(int i) { enter(IM); state[i] = hungry; test(i); if(state[i]!=eating) wait(self[i] , self_count[i] , IM) leave(IM); } procedure putdown(int i) { enter(IM); state[i]=think; test((i-1)%5); test((i+1)%5); leave(IM); } procedure test(int k) { if((state[(k-1)%5]!=eating)&&(state[k]==hungry)&&(state[k+1]!=eating)) { state[k] = eating; signal(self[k] , self_count[k] , IM); } }
想吃时调用过程pickup,吃完调用过程putdown。
霍尔管程解决生产者-消费者问题:
type producer_consumer = monitor { item B[k]; int in , out; int count; cond notfull , notempty; notfull = notempty = 0; int notfull_count , notempty_count ; notefull_count = notempty_count = 0; InterfaceModule IM; define append , take; use enter , leave , wait , signal; } procedure append(item &x) { enter(IM); if(count==k) wait(notfull , notfull_count , IM); B[in] = x; in = (in+1)%k; count++; signal(notempty , notempty_count , IM); leave(IM); } procedure take(item &x) { enter(IM); if(count==0) wait(notempty , notempty_count , IM); x = B[out]; out = (out+1)%k; count—; signal(notfull , notfull_count , IM); leave(IM); } cobegin process producer_i() { item x; produce(x); producer_consumer.append(x); } process consumer_i() { item x; producer_consumer.take(x); consume(x); } coend
霍尔管程解决读者-写者问题:
TYPE read-writer = MONITOR var Rc, Wc : integer; R, W : semaphore; rc, wc: integer; define start_read, end_read, start_writer, end_writer; use wait, signal, check, release; begin rc:=0; wc:=0; Rc:=0; Wc:=0; R:=0; W:=0; end; procedure start_read; begin if wc>0 then wait(R,Rc,IM); rc := rc + 1; signal(R, Rc, IM); end; procedure end_read; begin rc := rc - 1; if rc=0 then signal(W,Wc,IM); end; procedure start_write; begin wc := wc + 1; if rc>0 or wc>1 then wait(W,Wc,IM); end; procedure end_write; begin wc := wc - 1; if wc>0 then signal(W,Wc,IM); else signal(R, Rc,IM); end;