上篇的連接在這裏哦:互斥那點事兒(上)編程
「我找到好辦法了!」併發
沒有想到,說話的人居然是磁盤!操作系統
進程調度器瑟瑟的說:「你有方法?仍是算了吧,我怕用你的方法操做系統要亂套了。」.net
磁盤委屈的道:「不就是剛剛冤枉你了嗎,這麼小氣幹什麼!再說了,這個方法不是我想出來的,是我從文件裏找到的。」線程
操做系統挑了挑眉毛:「哦?你找到什麼文件了,讓你們也瞅瞅?」code
磁盤嗡嗡的轉起來,很快就把文件取出來了。blog
「噹噹噹當~ 這但是大師 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 環境高級編程」這本書裏寫到「。。。三種特性形成了這種並不是必要的複雜性」,對於通常的互斥操做,仍是建議使用互斥鎖(固然是阻塞而非忙等待)。稍微複雜點的鎖還有「讀寫鎖」,之後有機會再講吧~
以爲我寫的還不錯的話,就點個贊吧!
若是本文對你有幫助,歡迎關注個人公衆號 tobe的囈語 ,帶你深刻計算機的世界~ 公衆號後臺回覆關鍵詞【計算機】有驚喜哦~