经典同步问题一——生产者和消费者问题

系列同步问题:
经典同步问题一——生产者和消费者问题
https://blog.csdn.net/weixin_36465540/article/details/105560002
经典同步问题二——哲学家进餐问题
https://blog.csdn.net/weixin_36465540/article/details/105564907
经典同步问题三——读者写者问题
https://blog.csdn.net/weixin_36465540/article/details/105565495

不懂得结构型信号量的小伙伴可参考下面博文,之后再阅读本博文,更易于理解
https://blog.csdn.net/weixin_36465540/article/details/105559848

生产者和消费者问题

问题描述

一个或多个生产者产生数据并放在缓冲区中,每次一个。
一个或多个消费者从缓冲区取数据项并消费,每次一个。
条件:
1 在任意时间只能一个生产者或消费者访问缓冲区——互斥
2 保证生产者不能向满缓冲区中放产品
3 保证消费者不能从空缓冲区中取产品

Q:为什么缓冲区要互斥?
A:举例说明:环形缓冲区,用in指针指向将要放产品的缓冲区下标,out指针指向将要取出产品的缓冲区下标。对应一下put和take操作:

put(a)
{
	buffer[in] = a;		// 将A放入缓冲区
	in = (in+1) % N;	// N是缓冲区长度。例如下标0-6,长度为7
}

take()
{
	a = buffer[out];	// 将A从缓冲区取出
	out = (out+1) % N;
	return(a);
}

如果有两个进程P1和P2,在P1将a写入buffer[in]中后,in还未来得及改变的时候,P2又将B写入buffer[in],则出错。因此缓冲区需要互斥访问。

生产者和消费者问题的解决

不考虑条件的话,问题如下:

void producer() {
	while(true) {
		a = produce();	// 生产一个产品a
		put(a);			// a放入到缓冲区
	}
}

void consumer() {
	while(true) {
		a = take();		// 从缓冲区取出产品a
		consume(a);		// 消费掉产品a
	}
}

考虑实现互斥条件,只需设置信号量S.value=1,并在缓冲区操作前加上wait(a),缓冲区操作后加入signal(a)。

Semaphore S.value = 1;
void producer() {
	while(true) {
		a = produce();	// 生产一个产品a
		
		wait(S);
		put(a);			// a放入到缓冲区
		signal(S);
	}
}

void consumer() {
	while(true) {
		wait(S);
		a = take();		// 从缓冲区取出产品a
		signal(S);
		
		consume(a);		// 消费掉产品a
	}
}

考虑实现缓冲区为空时不能取产品,则必须在至少一个生产者实现put(a)操作后,才能执行take(a)操作。只需设置信号量N.value=0,在put之后执行signal(N),在take之前执行wait(N)。此时N.value仿佛一个计数器,记录着缓冲区产品的个数,只要这个值大于零,消费者就可以消费,而且在消费者消费之前,N.value要减1,即take之前要先执行wait(N)。
这里有两个问题,为什么signal(N)要放在signal(S)之后,为什么wait(N)要放在wait(S)之前。
先说第一个问题,为什么signal(N)要放在signal(S)之后?
如果交换signal(N)和signal(S)的位置,先执行signal(N),后执行signal(S),则此时signal(N)是在临界区内执行。此时可能出现以下情况:如果一个消费者因执行wait(N)而正在等待,则此时执行signal(N)将会被唤醒。而此时生产者还没有从临界区出来,则消费者会因为执行wait(S)而继续被阻塞。由此可见,交换signal(S)和signal(N)不会出错,但是从优化角度来说,signal(S)在前更好。
再说第二个问题,为什么wait(S)要放在wait(N)之后?
如果交换wait(S)和wait(N)的位置,先执行wait(S),后执行wait(N),则此时我们把wait(N)放入了临界区中。我们知道wait操作在某种情况下会导致进程进入等待状态。现在假设我们在初始情况下先执行consumer(),执行wait(S)时,由于S.value的初值是1,则可以进入临界区,再执行wait(N)时,由于N.value=0,则会导致进程阻塞。而我们发现,能够唤醒wait(N)的signal(N)操作,必须在使用临界区之后才能执行,那么这种情况下,临界区被阻塞,生产者永远不可能进入临界区,也就永远不可能执行signal(N),阻塞将一直存在,即产生了死锁。这种次序的交换会引起错误。

Semaphore S.value = 1;
Semaphore N.value = 0;
void producer() {
	while(true) {
		a = produce();	// 生产一个产品a
		
		wait(S);
		put(a);			// a放入到缓冲区
		signal(S);
		signal(N);
	}
}

void consumer() {
	while(true) {
		wait(N);
		wait(S);
		a = take();		// 从缓冲区取出产品a
		signal(S);
		
		consume(a);		// 消费掉产品a
	}
}

考虑实现缓冲区满时不能再放入产品。可以设置一个信号量E.value=MAX,每向缓冲区放1个产品,E.value减1,当值减为0,说明不能再放入产品。也就是要在put前面加上wait(E)操作。同理,每消费一个产品,E.value加1。即在take之后加上signal(E)操作。
这里同样存在上文所说的两个问题。即signal(E)为什么要在signal(S)之后,wait(E)为什么要在wait(S)之前。原因也如上文所述。同样交换signal不会出错,交换wait会产生死锁。

Semaphore E.value = MAX;
Semaphore S.value = 1;
Semaphore N.value = 0;
void producer() {
	while(true) {
		a = produce();	// 生产一个产品a
		
		wait(E);
		wait(S);
		put(a);			// a放入到缓冲区
		signal(S);
		signal(N);
	}
}

void consumer() {
	while(true) {
		wait(N);
		wait(S);
		a = take();		// 从缓冲区取出产品a
		signal(S);
		signal(E);
		
		consume(a);		// 消费掉产品a
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值