Produce-Consumer Q / Exclusive

生产者和消费者问题是进程同步比较经典的问题之一.

问题是这样的:

一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费,为使生产者进程和消费者进程能并发进行,在这两个之间设置了一个具有n个buffer的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中,消费者进程可从一个缓冲区中取走产品去消费,虽然生产者和消费者都可以按照异步方式运行,但是它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已经满的缓冲区中添加产品.

我们来分析下这个过程:

使用一个拥有n个缓冲区的缓冲池,用输入in来指向可以添加产品的buffer, 每当生产者进程生产并添加一个产品后,输入指针加1,用一个输出指针out来指向可以获取的缓冲区,每当消费者进程取走一个产品后,输出指针+1 ,这里的buffer是个循环buffer. 所以输入指针加可以表示成 in  = (in+1) mod n

Produce-Consumer Q / Exclusive

当 (in + 1)  mod n = out时表示缓冲区满,

Produce-Consumer Q / Exclusive


而in = out 表示缓冲区空

Produce-Consumer Q / Exclusive

这些其实也就是 环形队列 的重点了. 

继续思考生产者和消费者问题...

每当生产者进程向缓冲区投放一个产品后,就让 counter这个变量 + 1 反之 ,每当消费者进程从中使用一个产品后, counter - 1 .指针 in 和 out 初始化为 1 . 让我们来用伪码表示下这个过程.

/* --- By Crazybaby --- */
Producer:
while (1) {
	 TempPro tPro = new TempPro;					/**< 生产出一个产品 */
	 while (counter == n) {		/**< 如果当前counter数等于buffer容量则执行空循环 */
		  ;
	 }
	 buffer[in] = tPro;			/**< tPro临时存放生产出来的产品 */
	 in = (in + 1) % n;
	 counter += 1;
}

Consumer:
while (1) {
	 while (counter == 0) {		/**< buffer数目为空时,执行空语句 */
		  ;
	 }
	 tCon = buffer[out];
	 out = (out + 1) % n;
	 counter -= 1;
}

一切看来都很好,但是这里隐晦着一个严重的bug,顺序执行是一点都没,若进行彼岸发执行就会出现错误,原因就在于 全局变量 counter .先执行生产者 执行完再执行 消费者 是没有问题的. 来看看这种情况:

设 counter 初始值为 3

produceCounter = counter ;		/**< 3 */
produceCounter += 1;			/**< 4 */
consumerCounter = counter;				/**< 3 */
consumerCounter -= 1;			/**< 2 */
counter = produceCounter;		/**< 4 */
counter = consumerCounter;		/**< 2 */
								
正确的值应该为 3, 现在得出的值为 2,counter作为全局变量 ,这种情况就要考虑把counter作为临界区处理. 让生产者和消费者 互斥 的访问变量 counter.


二元信号量 解决生产者和消费者问题

二元信号量是只有0 和 1的信号量. 这里 n 记录缓冲区中数据项的个数 , 信号量 s 用于 互斥, 信号量 delay 用于 缓冲区为空时 或者 满 时 等待.

int n ;
Semaphore s = 1, delay 0;
void producer() {
	 while (1) {
		  produce();
		  semWaitB(s);
		  append();
		  n++;
		  if (1==n) {semSignalB(delay);}
		  semSignalB(s);
		  
	 }
}
void Consumer() {
	 int m;
	 semWaitB(delay);
	 while (1) {
		  semWaitB(s);
		  take();
		  n--;
		  m = n;
		  semSignalB(s);
		  consume();
		  if (0==m) {semWaitB(delay);}
		  
	 }
}
这里有两个原语要解释下:
semWait 和 semSignal原语被假设是原子操作, 原型如下:

struct Semaphore {
	 enum {zero, one} value;
	 queueType queue;
};
void semWaitB (Semaphore s) {
	 if (s.value == one) {
		  s.value = zero;
	 }
	 else
	 {
		  /* --- 把当前的产品插入到缓冲区中 --- */
		  /* --- 注射当前进程 --- */
	 }
		
}
void semSignalB(Semaphore s) {
	 is (s.queue is empty()) {
		  s.value = one;
	 }
	 else
	 {
		  /* --- 把进程p从等待队列中移除 --- */
		  /* --- 把进程p插入到就绪队列 --- */
	 }
}

所以信号量的互斥模型可以表示如下:

while (1) {
	 semWait(s);
	 /* --- 临界区 --- */
	 semSignal(s);
	 /* --- Others --- */
}

每个进程进入临界区前先执行semWait(s),如果s为负,则进程被挂起,如果值为1,则s-1 ,则进入临界区.信号量一般开始都初始化为1,这样第一个执行semWait的进程就可以立即进入临界区,并把s的值置为0,然后其它想进入临界区的进程都会发现0-1后为-1,为负 则挂起进程 每个都不能成功进入的进程都执行一次s-1.若离开时则s+1 这里就是所谓的互斥了.





原文链接: http://blog.csdn.net/crazyjixiang/article/details/6706129

你可能感兴趣的:(Produce-Consumer Q / Exclusive)