互斥那點事兒(下)


“我找到好辦法了!”

沒有想到,說話的人竟然是磁盤!

進程調度器瑟瑟的說:“你有方法?還是算了吧,我怕用你的方法操作系統要亂套了。”

磁盤委屈的道:“不就是剛剛冤枉你了嗎,這么小氣干什么!再說了,這個方法不是我想出來的,是我從文件里找到的。”

操作系統挑了挑眉毛:“哦?你找到什么文件了,讓大家也瞅瞅?”

磁盤嗡嗡的轉起來,很快就把文件取出來了。

“當當當當~ 這可是大師 Dijkstra 的論文,他引入了一個全新的變量類型——信號量(semaphore)。然后還為信號量設置了兩種操作,P(proberen,檢測) 和 V(verhogen,增量) 。”

”說清楚點啊,信號量是怎么個用法啊?“進程急切的問道。

“別急,讓我接着看。。。Dijkstra 提出,P操作是檢測信號量是否為正值,如果不是,就阻塞調用進程。 V操作能喚醒一個阻塞進程,讓他恢復執行 。具體點的話就是這樣: “

// S 為信號量
P(s):
{
S = S - 1
if (S < 0)
	{
		調用該 P 操作的進程阻塞,並插入相應的阻塞隊列;
	}
}
// S 為信號量
V(s):
{
S = S + 1
if (S <= 0)
	{
		從等待信號量 S 的阻塞隊列里喚醒一個進程;
	}
}

內存仔細看了代碼,說:”這個實現也要求是原子操作誒,Dijkstra 這個方法很有趣啊。“

進程蒙圈了:“我怎么完全看不懂啊?內存你給我講講唄。”

“好,我就用最簡單的一組線程舉例子了:

// 線程 A,B,C , S = 1
...
P(S) 		//S = S - 1  若 S < 0 ,阻塞等待
購票操作
V(S)		//S = S + 1  若 S <= 0, 表明有線程阻塞了,得喚醒其中一個 
...

這里的 「購票操作」 就是我們要保護的臨界區,我們要保證一次只能有一個線程進入。那我們就把 S 的初始值設為 1 。當線程 A 第一個調用 P(S) 后,S 的值就變成了 0 ,A 成功進入臨界區。在 A 出臨界區之前,線程 B 如果調用 P(S), S 就變成 -1 ,滿足 S < 0 的判斷條件,線程 B 就被阻塞了。等 A 調用 V(S) 后,S 的值又變成 0 ,滿足 S <= 0,就會把線程 B 喚醒,B 就能進入臨界區了。“

進程恍然大悟:“原來是這樣,看起來和二元鎖差不多啊,但是不用忙等待了。”

內存神秘一笑:“信號量能做的可不止這些,你想想看,要是我把 S 的初始值設為 2 ,會發生什么?”

“一次能有兩個線程訪問臨界區!”進程這次反應快多了:“也就是說 S 的初始值可以控制有多少個線程進入臨界區,太厲害了!”

tobe 注:從信號量的值能看出還有多少個進程能進入臨界區,如果為負數,表明有 x 個進程因為調用 P(S) 而被阻塞

“沒錯,所以說信號量是一個很靈活的並發機制。而且信號量還有另一個厲害的用處:

你看這兩個進程有什么特別的地方?“

“emmmm,這個嘛,進程 P2 的 V 操作居然放在 P 操作的前面,而且兩個操作的信號量還不是同一個。”

“沒錯,這樣使用信號量,能讓兩個進程做到同步。你看,如果 P1 運行到 P(S1),他是不是會阻塞?”

進程認真一看,說:“沒錯誒,S1 初始值是 0,P1 肯定得停在這一句。讓我再看看,,,如果 P1 想接着運行,就得等 P2 調用 V(S1) 把他喚醒。”

“是的,這就是同步——運行快的 P1 必須在這里停下來等 P2 運行到指定位置。兩個進程的執行順序就是這樣:

也就是說 x 最終的值必然是 30,而不可能是 20。在信號量的幫助下,這兩個進程達成了同步。“

進程由衷的感嘆:“信號量實在是太強大了!咱們以后就用信號量來解決互斥的問題吧!”


tobe 注:在 Linux 里提供了信號量互斥量(也就是二元鎖)這兩種主要機制實現互斥,不過 Linux 的信號量功能要比文章里講得復雜得多,「UNIX 環境高級編程」這本書里寫到「。。。三種特性造成了這種並非必要的復雜性」,對於一般的互斥操作,還是建議使用互斥鎖(當然是阻塞而非忙等待)。稍微復雜點的鎖還有「讀寫鎖」,以后有機會再講吧~

覺得我寫的還不錯的話,就點個贊吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM