經典同步問題一——生產者和消費者問題

系列同步問題:
經典同步問題一——生產者和消費者問題
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
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章