经典进程同步问题一:生产者-消费者问题(The producer-consumer problem)
(注:参考教材:计算机操作系统第四版 西安电子科技大学出版社)
问题描述:一群生产者进程在生产产品,并将这些产品提供给消费者去消费。为了使生产者进程与消费者进程能够并发进行,在两者之间设置一个具有n个缓冲区的缓冲池,生产者进程将产品放入一个缓冲区中;消费者可以从一个缓冲区取走产品去消费。尽管所有的生产者进程和消费者进程是以异方式运行,但它们必须保持同步:当一个缓冲区为空时不允许消费者去取走产品,当一个缓冲区满时也不允许生产者去存入产品。
解决方案:我们这里利用一个一个数组buffer来表示这个n个缓冲区的缓冲池,用输入指针和输出指针+1来表示在缓冲池中存入或取出一个产品。由于这里的缓冲池是循环缓冲的,故应把in和out表示成:in = ( in +1 ) % n (或把out表示为 out = ( out +1 ) % n )当( in +1) % n= out的时候说明缓冲池满,in = out 则说明缓冲池空。在这里还要引入一个整型的变量counter(初始值0),每当在缓冲区存入或取走一个产品时,counter +1或-1。那么问题的关键就是,把这个counter作为临界资源处理,即令生产者进程和消费者进程互斥的访问它。
首先解释一下信号量(Semaphores)机制。这是荷兰学者Dijkstra在1965年提出的一种有效的进程同步工具。Dijkstra定义的整型信号量是一个用于表示资源数目的整型量,不同于一般的整型量,它除了初始化以外只能通过两个标准的原子操作(Atomic Operation)waits(S)和signal(S)来访问。很长时间以来这两个操作分别被称为P、V操作。wait和signal操作描述如下:
1 wait(S){ 2 3 while (S <= 0); //无可用资源 4 S-- ; 5 } 6 7 signal(S){ 8 9 S-- ; 10 }
waits(S)和signal(S)是原子操作,执行时不可中断。即:当一个进程在修改某个信号量时,没有其他进程可以对其修改。
1.利用记录型信号量解决问题
上述整型信号量机制中,只要S<=0就会不断的测试,没有遵循“让权等待”准则,可能导致“忙等”。记录型信号量机制采取“让权等待”,但会出现多个进程等待访问同一个临界资源的情况,因此在此机制中,除了一个表示资源数量的整型量value以外,还需要增加一个进程链表指针list,用于链接所有等待进程。
记录型信号量的数据项描述如下:
1 typedef struct{ 2 3 int value ; 4 struct processControlBlock *list ; 5 6 }semaphore;
相应的wait(S)和signa(S)为:
1 wait(semaphore *S){ 2 3 S->value -- ; 4 if( S->value < 0 ) 5 block( S->list ); //资源分配完毕,自我阻塞 6 7 } 8 9 signal(semaphore *S){ 10 11 S->value ++ ; 12 if( S->value <= 0 ) 13 weakup( S->list ); //仍有等待该资源的进程被阻塞,将list中的第一个进程唤醒 14 15 }
现在是对生产者-消费者问题解决方案的描述:
假定在生产者和消费者之间的公共缓冲池中具有n个缓冲区,利用互斥信号量mutex来实现诸进程对缓冲池的互斥使用;利用信号量empty和full分别表示缓冲池中的空槽数量和满槽数量。
1 int in = 0 , out = 0 ; 2 item buffer[n]; 3 semaphore mutex = 1 ; //信号量,控制对临界资源的互斥访问 4 semaphore empty = n ; //信号量,表示缓冲池当前的空槽数目 5 semaphore full = 0 ; //信号量,表示缓冲池当前的满槽数目 6 7 //producer 8 void producer { 9 10 do { 11 12 item = produce_item(); 13 wait(empty); //空槽数目减一 14 wait(mutex); //进入临界区 15 buffer[in] = insert_item(item); //存入产品 16 in = ( in +1 ) % n ; 17 signal(mutex); //退出临界区 18 signal(full); //满槽数目加一 19 20 } while(1) 21 } 22 23 //consumer 24 void consumer{ 25 26 do { 27 28 wait(full); //满槽数目减一 29 wait(mutex); //进入临界区 30 remove_item(item) = buffer[out] ; //取出产品 31 out = ( in +1 ) % n ; 32 signal(mutex); //退出临界区 33 signal(empty); //空槽数目加一 34 consumer_item(item); 35 } while(1) 36 } 37 38 void main() { 39 40 cobegin //并发执行 41 producer();consumer(); 42 coend 43 44 }
注意:
1.用于实现互斥的wait(mutex) 和signal(mutex)必须成对出现,而对资源的full和empty的wait和signal也要成对出现,这里是指分别出现在不同的程序中。
2.应该先进行对资源的wait操作再进行对互斥信号量的wait操作。
2.利用AND信号量解决问题
首先还是解释一下AND型信号量。上述是针对多个并发程序仅共享一个临界资源的情况,AND型信号量则是解决多个并发程序共享多个资源的问题。基本思想是:对若干个临界资源的分配采取原子操作的方法,要么把它请求的所有资源都分配到进程,要么就一个也不分配。这里用Swait()表示要么全部分配,要么全不分配,用Ssignal()表示全部释放。
Swait() 和Ssignal的描述如下:
1 Swait(S1,S2,S3,...,Sn){ 2 while (1) { 3 if( S1>=1 && S2>=1 && ... && Sn>=1){ 4 5 for( i = 1 ; i <= n ; i++) Si --; 6 break ; 7 }else{ 8 9 将这个进程放进等待队列,第一个<1的Si作为等待队列的开始 10 } 11 } 12 } 13 14 Ssignal(S1,S2,...,Sn) { 15 16 while(1) { 17 for( i = 1 ; i <= n ; i++) { 18 Si ++; 19 清空等待队列并将其存入就绪队列 20 } 21 } 22 }
具体实现:
1 consumer_item(item); 2 3 int in = 0 , out = 0 ; 4 item buffer[n]; 5 semaphore mutex = 1 ; //信号量,控制对临界资源的互斥访问 6 semaphore empty = n ; //信号量,表示缓冲池当前的空槽数目 7 semaphore full = 0 ; //信号量,表示缓冲池当前的满槽数目 8 9 //producer 10 void producer { 11 12 do { 13 14 item = produce_item(); 15 Swait(empty,mutex); 16 buffer[in] = insert_item(item); //存入产品 17 in = ( in +1 ) % n ; 18 signal(mutex,full); 19 20 21 } while(1) 22 } 23 24 //consumer 25 void consumer { 26 27 do { 28 29 wait(full,mutex); 30 remove_item(item) = buffer[out] ; //取出产品 31 out = ( in +1 ) % n ; 32 signal(mutex,empty); 33 consumer_item(item); 34 } while(1) 35 } 36 37 void main() { 38 39 cobegin //并发执行 40 producer();consumer(); 41 coend 42 43 }
3.利用管程解决问题
首先介绍一下管程。代表共享资源的数据结构以及对该数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,称为管程。
这里先定义一个管程PC,描述如下:
1 Monitor PC { 2 3 item buffer[N]; 4 int in , out ; 5 condition notfull , notempty; //条件变量 6 int count ; //缓冲池中已有的产品数目 7 8 public: 9 //没满就往里存,满了就阻塞 10 void put( item x ){ 11 12 if( count >= N) 13 cwait( notfull ); //缓冲池满,阻塞,挂在条件condition的队列上 14 buffer[in] = x; 15 in = ( in +1 ) % n ; 16 count ++ ; 17 csignal(notempty); // 唤醒一个阻塞在condition队列中的进程,队列空则无操作 18 } 19 20 void get( item x ){ 21 22 if( count <= 0) 23 cwait( notempty ); //缓冲池空,阻塞,挂在条件condition的队列上 24 x = buffer[out]; 25 out = ( out +1 ) % n ; 26 count -- ; 27 csignal(notfull); // 唤醒一个阻塞在condition队列中的进程,队列空则无操作 28 } 29 30 { in = 0, out = 0 , count = 0 ;} 31 32 }PC;
具体方案:
1 //producer 2 void producer(){ 3 4 item x; 5 while(1){ 6 7 x = produce_item(); 8 PC.put(x); 9 } 10 } 11 12 //consumer 13 14 void consumer(){ 15 16 item x; 17 while(1){ 18 19 PC.get(x); 20 consumer_item() = x ; 21 22 } 23 } 24 25 void main() { 26 27 cobegin //并发执行 28 producer();consumer(); 29 coend 30 31 }