什麼是線程安全?一文帶你深入理解

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎來到操作系統系列,採用圖解 + 大白話的形式來講解,讓小白也能看懂,幫助大家快速科普入門。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上篇文章有介紹過進程與線程的基礎知識,進程下擁有多個線程,雖然多線程間通信十分方便(同進程),但是卻帶來了線程安全問題,本篇主要就是介紹操作系統中是用什麼方法解決多線程安全,廢話不多說,進入正文吧。","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"博主希望讀者閱讀文章後可以養成思考與總結的習慣,只有這樣才能把知識消化成自己的東西,而不是單純的去記憶","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#內容大綱","title":null,"type":null}},{"type":"text","text":"內容大綱","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3a/3adf2b0122fc7d00ec0d7e0119a7bb6d.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#小故事","title":null,"type":null}},{"type":"text","text":"小故事","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"帶薪蹲坑,相信都是大夥都愛做的事情,阿星也不例外,但是我司所在的樓層的坑位較少,粥少僧多,十分煩惱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阿星(線程A)每次去廁所(共享資源),門都是鎖着的,說明有同事在裏面佔着坑(線程B持有鎖),只能無奈的在外面乖乖的等着,不久後沖水聲響起,同事爽完出來(線程B釋放鎖),阿星一個健步進入廁所把門鎖住(線程A持有鎖),享受屬於自己的空間,晚來的其他同事只能乖乖排隊,一切都是那麼井然有序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設門鎖壞了,井然有序就不存在了,上廁所不再是享受,而是高度緊張,防止門突然被打開,更糟糕的是,開門時,是個妹子,這下不僅僅是線程安全問題,還有數組越界了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"故事說完,扯了那麼多,就是想說明,在多線程環境裏,對共享資源進行操作,如果多線程之間不做合理的協作(互斥與同步),那麼一定會發生翻車現場。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#競爭條件","title":null,"type":null}},{"type":"text","text":"競爭條件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲多線程共享進程資源,在操作系統調度進程內的多線程時,必然會出現多線程競爭共享資源問題,如果不採取有效的措施,則會造成共享資源的混亂!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/38/38681579be1e9ec8427c8a000b6c2331.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來寫個小例子,創建兩個線程,它們分別對共享變量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 自增 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1000","attrs":{}}],"attrs":{}},{"type":"text","text":" 次,如下代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d36a5a37d52100ec8b0537722afb1c90.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正常來說,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 變量最後的值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2000","attrs":{}}],"attrs":{}},{"type":"text","text":" ,可是並非如此,我們執行下代碼看看結果","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2000","attrs":{}}],"attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1855","attrs":{}}],"attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運行了兩次,結果分別是1855、2000,我們發現每次運行的結果不同,這在計算機裏是不能容忍的,雖然是小概率出現的錯誤,但是小概率它一定是會發生的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#彙編指令","title":null,"type":null}},{"type":"text","text":"彙編指令","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了搞明白到底發生了什麼事情,我們必須要瞭解彙編指令執行,以 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲例子,彙編指令的執行過程如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1f/1f941c76856ca60e0b2ecaa8c9a1e51c.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好傢伙,一個加法動作,在 C P U 運行,實際要執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"3","attrs":{}}],"attrs":{}},{"type":"text","text":" 條指令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在模擬下線程A與線程B的運行,假設此時內存變量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 的值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",線程A加載內存的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值到寄存器,對寄存器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",此時 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",正準備執行下一步寄存器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值回寫內存,時間片使用完了,發生線程上下文切換,保存線程的私有信息到線程控制塊T C P。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系統調度線程B執行,此時的內存變量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 依然還是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",線程B執行與線程A一樣的步驟,它很幸運,在時間片使用完前,執行完了加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",最終回寫內存,內存變量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程B時間片使用完後,發生線程上下文切換,回到線程A上次的狀態繼續執行,寄存器中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值回寫內存,內存變量再次被設置成 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按理說,最後的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值應該是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2","attrs":{}}],"attrs":{}},{"type":"text","text":",但是由於不可控的調度,導致最後 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",下面是線程A與線程B的流程圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/770e57b821d6efe06602ff3f304f05b4.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步:內存取出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值,加載進寄存器","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步:對寄存器內的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三步:寄存器內的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值取出 加載進內存","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#小結","title":null,"type":null}},{"type":"text","text":"小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種情況稱爲競爭條件(race condition),多線程相互競爭操作共享資源時,由於運氣不好,在執行過程中發生線程上下文切換,最後得到錯誤的結果,事實上,每次運行都可能得到不同的結果,因此輸出的結果存在不確定性(indeterminate)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#互斥與同步","title":null,"type":null}},{"type":"text","text":"互斥與同步","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決因競爭條件出現的線程安全,操作系統是通過互斥與同步來解決此類問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#互斥概念","title":null,"type":null}},{"type":"text","text":"互斥概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多線程執行共享變量的這段代碼可能會導致競爭狀態,因此我們將此段代碼稱爲臨界區(critical section),它是執行共享資源的代碼片段,一定不能給多線程同時執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們希望這段代碼是互斥(mutualexclusion)的,也就說執行臨界區(critical section)代碼段的只能有一個線程,其他線程阻塞等待,達到排隊效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6e7ee8d72af4ff05b4c6d8d18d7d2fd8.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥並不只是針對多線程的競爭條件,同時還可用於多進程,避免共享資源混亂。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#同步概念","title":null,"type":null}},{"type":"text","text":"同步概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥解決了「多進程/線程」對臨界區使用的問題,但是它沒有解決「多進程/線程」協同工作的問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道在多線程裏,每個線程一定是順序執行的,它們各自獨立,以不可預知的速度向前推進,但有時候我們希望多個線程能密切合作,以實現一個共同的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂同步,就是「多進程/線程間」在一些關鍵點上可能需要互相等待與互通消息,這種相互制約的等待與互通信息稱爲「進程/線程」同步。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉個例,有兩個角色分別是研發、質量管控,質量管控測試功能,需要等研「發完成開發」,研發要修bug也要等質量管控「測試完成提交B U G」,正常流程是研發完成開發,通知質量管控進行測試,質量管控測試完成,通知研發人員修復bug。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b9/b9705a6119c71f633fc0c101128b2e27.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#互斥與同步的區別","title":null,"type":null}},{"type":"text","text":"互斥與同步的區別","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥:某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的(操作 A 和操作 B 不能在同一時刻執行)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步:互斥的基礎上,通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥(操作 A 應在操作 B 之前執行,操作 C 必須在操作 A 和操作 B 都完成之後才能執行)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顯然,同步是一種更爲複雜的互斥,而互斥是一種特殊的同步。也就是說互斥是兩個線程之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行,而同步也是不能同時運行,但他是必須要按照某種次序來運行相應的線程(也是一種互斥)!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#互斥與同步的實現","title":null,"type":null}},{"type":"text","text":"互斥與同步的實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥與同步可以保證「多進程/線程間正確協作」 ,但是互斥與同步僅僅只是概念,操作系統必須要提供對應的實現,針對互斥與同步的實現有下面兩種","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鎖:加鎖、解鎖操作(互斥)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信號量:P、V 操作(同步)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩個種方式都可以實現「多進程/線程」互斥,信號量比鎖的功能更強一些,它還可以方便地實現「多進程/線程」同步。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#鎖","title":null,"type":null}},{"type":"text","text":"鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顧名思義,給臨界區上一把鎖,任何進入臨界區)的線程,必須先執行加鎖操作,加鎖成功,才能進入臨界區,在離開臨界區時再釋放鎖,達到互斥的效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1ae9e80742dda66993fab20d09ae60f3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鎖的實現方式又分爲「忙等待鎖」和「無忙等待鎖」","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#忙等鎖","title":null,"type":null}},{"type":"text","text":"忙等鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢查並設置(test-and-set-lock,TSL)是一種不可中斷的原子運算,它屬於原子操作指令,可以通過它來實現忙等鎖(自旋鎖)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"test-and-set-lock 指令僞代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/de/de1bb9c92a3c7e7cdec2a9313d73f8e3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢查並設置做了如下幾個步驟","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢查舊值是否相等","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相等設置新值,返回原舊值(成功)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不相等,無任何操作,直接返回原舊值(失敗)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的步驟,把它看成一步並具備原子性,原子性的意思是指全部執行或都不執行,不會出現執行到一半的中間狀態.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"僞代碼","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"testAndSetLock","attrs":{}}],"attrs":{}},{"type":"text","text":"實現忙等鎖(自旋鎖)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1b/1b80d15f14fd9c25f78d4bd5ecd31f3b.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面兩種場景運行","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單線程:假設一個線程訪問臨界區,執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,檢查舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 通過,更新原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",返回原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",獲取鎖成功,離開臨界區時,執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,檢查舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 通過,更新原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",釋放鎖成功。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多線程:假設兩個線程,線程A訪問臨界區,執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,檢查舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 通過,更新原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",返回原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",獲取鎖成功,此時線程B執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,舊值檢查失敗,獲取鎖失敗,一直循環直到更新成功爲止,當線程A離開臨界區時,執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,檢查舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 通過,更新原舊值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",釋放鎖成功,線程B獲取鎖成功。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當獲取不到鎖時,線程就會一直 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wile","attrs":{}}],"attrs":{}},{"type":"text","text":" 循環,不做任何事情,所以就被稱爲忙等待鎖,也被稱爲自旋鎖。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是最簡單的鎖,一直自旋,利用 C P U 週期,直到鎖可用。在單處理器上,需要搶佔式的調度器(即不斷通過時鐘中斷一個線程,運行其他線程)。否則,自旋鎖在 C P U 上無法使用,因爲一個自旋的線程永遠不會放棄 C P U。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#無忙等鎖","title":null,"type":null}},{"type":"text","text":"無忙等鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顧名思義,無忙等鎖不需要主動自旋,被動等待喚醒即可,在沒有獲取到鎖的時候,就把該線程加入到等待隊列,讓出 C P U 給其他線程,其他線程釋放鎖時,再從等待隊列喚醒該線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/78/78da1292367b5cd5300c717f256e9eaf.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩種鎖的實現都是基於檢查並設置(test-and-set-lock,TSL),上面只是簡單的僞代碼,實際上操作系統的實現會更復雜,但是基本思想與大致流程還是與本例一樣。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#信號量","title":null,"type":null}},{"type":"text","text":"信號量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系統中協調「多線程/進程」共同配合工作,就是通過信號量實現的,通常信號量代表「資源數量」,對應一個整型(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":")變量,還有兩個原子操作的系統調用函數來控制「資源數量」。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"P 操作:將 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":" 減 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",相減後,如果 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":" 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章