【操作系統】第九章同步互斥問題

以下是操作系統的各部分知識點的目錄:
第一章:操作系統的概述
https://blog.csdn.net/weixin_44751294/article/details/104172847
第二章:啓動、中斷、異常和系統調用
https://blog.csdn.net/weixin_44751294/article/details/104172778
第三章:續內存分配
https://blog.csdn.net/weixin_44751294/article/details/104183639
第四章:非連續內容分配
https://blog.csdn.net/weixin_44751294/article/details/104190197
第五章:虛存技術
https://blog.csdn.net/weixin_44751294/article/details/104196572
第六章:頁面置換算法
https://blog.csdn.net/weixin_44751294/article/details/104204687
第七章:進程與線程
https://blog.csdn.net/weixin_44751294/article/details/104228830
第八章:處理機調度
https://blog.csdn.net/weixin_44751294/article/details/104229011
第九章:同步互斥問題
https://blog.csdn.net/weixin_44751294/article/details/104235209
第十章:信號量與管程
https://blog.csdn.net/weixin_44751294/article/details/104248160
第十一章:死鎖與進程通信
https://blog.csdn.net/weixin_44751294/article/details/104259732
第十二章:文件系統
https://blog.csdn.net/weixin_44751294/article/details/104268956

本章節只要解決的是同步的問題

9.1背景知識

1、如果資源處理不當,可能會出現一些意想不到的情況,合作的風險
獨立的線程:

  • 不和其他線程共享資源或狀態
  • 確定性->輸入狀態決定結果
  • 可重現->能夠重現起始條件
  • 調度順序不重要

合作線程:

  • 在多喝線程中共享狀態
  • 不確定性
  • 不可重現(不可重複性)

這些不確定性和不可重複以意味着bug可能是間歇性發生的,也就是合作是有風險的。

2、爲什麼要合作
1)共享資源
資源是需要共享的,因爲進程可能要訪問同一個文件。
2)加速
通過並行和併發,可以提高系統的效率,實現更有效的資源的利用。相當於把一個大的任務,拆分成多個小的任務,每個任務通過並行的執行提高系統的性能。
3)模塊化
在設計時將一個大的工作,變成一個小的工作,使之具有模塊化,使系統便於擴展。

3、問題出現的原因
例子:
在這裏插入圖片描述
以上四條彙編指令的意思是:
1)把next_pid賦值給寄存器1(Reg1)
2)再把這個寄存器1存到了new_pid這個內存單元的去。此時new_pid就具有了next_pid這個值。
3)寄存器1加一操作。
4)完成next_pid的值增加了一個1的操作。
總的實現過程:
先把new_pid = next_pid,然後next_pid再加1.

但是,如果這時有兩個進程,就會出現意想不到的情況:
在這裏插入圖片描述
問題產生的原因:
在第二次進程的上下文切換時候,進程1的寄存器恢復之後依然100的值,是的next的值無法更新稱爲102。最終產生了切換使得最終的結果不是想要的結果。這是一種典型的異常現象。

9.2一些概念part1

由於上述產生的異常現象(稱之爲競態條件Race Condition),這就是爲什麼要引入同步互斥這些機制的原因,就是要解決這種不確定性的問題。

1、系統缺陷:結果依賴於併發執行或者事件的順序/時間
不確定性
不可重現

2、怎樣避免競態?
讓指令不被打斷(比如上述的四條機器指令不被打斷)

3、不被打斷的方法:原子操作(Atomic Operation)—不可被打斷操作
原子操作是指一次不存在任何中斷或者失敗的執行

  • 該執行成功結束
  • 或者根本沒有執行
  • 並且不應該發現任何部分執行的狀態

實際上操作往往不是原子的

  • 有些看上去是原子操作,實際不是
  • 連x++這樣簡單的語句,實際上是由3條指令造成的
  • 有時候甚至連條單條機器指令都不是原子的

例子:
在這裏插入圖片描述
所以需要後續的同步機制,確保或者是A贏或者是B贏。

4、一些基本概念

  • 臨界區(Critical section)

臨界區是指進程中的一段需要訪問共享資源並且當另一個進程處於相應代碼區域時便不會被執行的代碼區域。簡單來說,就是訪問共享資源的那段代碼就是臨界區。

  • 互斥(Mutual exclusion)

當一個進程處於臨界區並訪問共享資源時,沒有其他進程會處於臨界區並且訪問任何相同的共享資源。

  • 死鎖(Dead lock)

兩個或以上的進程,在互相等待完成特定任務,而最終沒法將自身任務進行下去。

  • 飢餓(Starvation)

一個可執行的進程,被調度器持續忽略,以至於雖然處於可執行狀態卻不被執行

9.3一些概念part2

1、一個有趣的類比:
在這裏插入圖片描述
2、解決的方法和概念
在這裏插入圖片描述
3、更好的解決方法(輕量級)
在這裏插入圖片描述
但是由於進程上下文切換的原因,問題還是會存在。
在這裏插入圖片描述
如果只是將Note往前面提簡單的挪動一下還是不會解決問題,變成誰都不會去買麪包了。
在這裏插入圖片描述

9.4一些概念part3

1、再換一個方法
在這裏插入圖片描述
結果還是有問題的。
在這裏插入圖片描述
需要確保在任何情況下,只有一個進程在臨界區中執行,其他的進程需要在外面等待。

一個更加合理的方案解析過程:
在這裏插入圖片描述
程序是有效的,但是導致代碼不一樣了。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
最終的解決方案:
爲每一個線程保護了一段“臨界區”代碼。使用臨界區的思想,問題就可以較好的解決。其是講前訴方法的一個抽象。
有了臨界區的代碼之後,就可以確保任何時候只有一個進程在臨界區中執行,切其他進程在外面等待,知道臨界區中的進程離開,其他進程中的一個會進入臨界區去執行。這個是比較合理的一個實現。

9.5臨界區

在臨界區中執行所擁有的屬性:
1、互斥:同一個時間臨界區中最多存在一個線程
2、前進(Progress):如果一個線程想要進入臨界區,那麼它最終會成功,不會一直的死等。
3、有限等待:如果一個線程i處於入口區,那麼在i的請求被接受之前,其他線程進入臨界區的時間是有限制的。如果是無限等待,就會出現飢餓狀態,是Progress前進狀態的一種進一步補充。
4、忙等(可選屬性):如果一個進程在等待進入臨界區,那麼在它可以進入之前會被掛起。

基於這些屬性,設計一些方法對臨界區進行保護:
方法一:禁用硬件中斷
方法二:基於軟件的解決方法
方法三:更高級的抽象(基於硬件原子操作的指令)

9.6禁用硬件中斷

一、基本實現
沒有中斷,也就是沒有了上下文切換,因此沒有併發。

  • 硬件將中斷處理延遲到中斷被啓用之後
  • 大多數現代計算機體系結構都提供指令來完成

進入臨界區時禁用中斷,離開臨界區時開啓中斷。這個方法是可以解決問題的。

二、缺點
1、一旦中斷被禁用,線程就無法被停止
2)整個系統都會爲你停下來
3)可能導致其他線程處於飢餓狀態

2、要是臨界區可以任意長
無法限制響應中斷所需的時間(可能存在硬件影響)

需要注意:
執行這種課屏蔽中斷的指令,只是把自身的響應中斷的能力屏蔽了,並不意味着也將其他cpu的響應中斷能力屏蔽,所以其實其他的cpu還是可以繼續產生中斷,所以在多cpu的情況下是無法解決互斥問題的。

9.7基於軟件的解決方案

一、思考方案
1、思考方案一:
某一個進程,它想進入臨界區,其有一個順序(次序),根據這個次序決定誰會進入這個臨界區。

方法如下所示:
在這裏插入圖片描述
假設這個線程的次序是0,那麼當turn=0時,纔去繼續下面的執行臨界區代碼,否者在while循環中一直打轉。條件滿足時,改變使得turn=1。
而進程1的代碼也是類似的,只是while循環中的判斷條件是不等於0,下面的turn=0.

這個程序的弊端是:
必須進程1執行一次臨界區,進程0執行一次臨界區,然後兩個交替執行,才能保證兩繼續的執行。一旦其中的一個進程不願意再做這個事情,那按照之前的屬性,其他進程先進去就應該能夠進去,但是在這種模式下,就無法完成這個前進的屬性。

2、思考方案二:
前面表示了一個turn是不夠表示,所以接下來使用一個小數組flag[2]來表示這個進程是否想進入臨界區。

  • flag[0] = 1 //表示進程0想進入臨界區執行
  • flag[0] = 1 //表示進程1想進入臨界區執行

方法1如下所示:
在這裏插入圖片描述
但是這個代碼是有問題的,不能滿足互斥這個屬性。
因爲在初始的時候,兩個進程都不會想進入臨界區,所以兩個flag都會賦值爲0,表面沒有這個需求。這樣就是的兩個進程都會跳出這個循環,然後都會將直接複製爲,想要進入臨界區,也就出現了多買麪包的想象。

方法2如下所示:
在這裏插入圖片描述
滿足了互斥,但是倘若兩個線程都賦值了1,出現上下文切換的時候,都無法跳出這個循環,也就是出現是死鎖的情況。

可見,互斥的解決並沒有想象的那麼簡單~~~

二、正確實現
1、正確的接法
將以上的兩種思考都綜合起來使用。三個變量共同的作用。
在這裏插入圖片描述
算法如下:
在這裏插入圖片描述
該算法可以滿足互斥,前進和有限等待三個屬性。
反證法來證明:
假定,現在兩個進程都進入了臨界區,都在執行臨界區代碼,但是turn只是一個值的,所以總會有一個線程會跳出循環的。

2、另外一種算法
所需的變量空間相同,但是更加的複雜
在這裏插入圖片描述
三、拓展
1、n進程解決方法1 (E&M算法)
除了了針對兩個進程之外,還可以拓展到n個進程如何保證互斥。
在這裏插入圖片描述
大致的思路:
對於進程i而言,對於其前面的進程而言,如果有進程想進入臨界區,或者已經進入了臨界區,那麼i進程就必須等待。而對於i進程後面的進程,必須要等待i進程執行之後在進入臨界區。這樣就可以通過一個循環的方式完成n個進程有序的進入臨界區。

2、n進程解決方法2(Bakery算法)
大致思路如下:
在這裏插入圖片描述
四、總結
1)即使是針對兩個進程的解決競態的實現還是比較複雜的。
2)需要忙等待,浪費cpu時間。
3)沒有硬件包裝的情況下無真正的軟件解決方案。對硬件的1需求比較低(只需要load操作和store是原子操作即可)

9.8更高級的抽象 — 基於原子操作

軟件的處理比較複雜有沒有更加簡單的實現方法?
一、基礎
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
使用了一些特殊的操作:
1、Test-and-Set測試和置位
(一條機器指令,但是完成了讀寫操作兩條指令的功能)
從內存中讀取值
測試該值是否爲1(然後返回真或假)
內存值設置爲1

2、交換
(交換內存中的兩個值)

只要計算機系統提供了這兩條的其中一條指令就可以很容易的完成互斥的問題。

二、解決的方法
1、Test-and-Set方式
在這裏插入圖片描述
解決忙等的情況:先讓其睡眠,在加一步喚醒操作
在這裏插入圖片描述
兩者的區別:

  • 忙等:不需要上下文切換,但是利用率低,適用與臨界區執行時間短的情況。
  • 不忙等:需要上下文切換,上下文切換開銷比較大大,適用於臨界區很長,遠遠大於上下文切換所需要的開銷。

2、交換的方式
在這裏插入圖片描述
解析:
1)當一個進程想要進入臨界區的時候,key=1,而且lock的初始值是0,所以當執行到while循環的時候,由於執行了交換,交換執行的過程不會被打斷進行上下文切換的操作,而後lock的變成了1,而key變成了0.所以會退出循環,執行臨界區的代碼。
2)需要注意的是,當進入臨界區的時候,load已經是1,當其他進程進入臨界區執行的時候,load是1,而key也是1,交換之後還是1,一直會循環的等待,進入不了臨界區。知道進入臨界區的進程,退出臨界區之後,完成一個將load變成0的操作。其他等待的進程纔會繼續執行exchange。

三、採用這種原子操作的特點
1、優點
1)簡單並且容易證明
2)適用於單處理器或者共享主存的多處理器中任意數量的進程
3)可以很容易拓展n個進程,可以用於支持多臨界區
4)開銷比較小

2、缺點
1)忙等待消耗處理器時間
2)當進程離開臨界區並且多個進程在等待的時候可能導致飢餓現象
3)出現死鎖的情況(例子:如果一個低優先級的進程擁有臨界區並且一個高優先級進程也需求,那麼高優先級進程會獲得處理器並且等待臨界區 — 需要用優先級反轉的方式進行處理)

四、總結
1、鎖是更高級的編程抽象

  • 互斥可以使用鎖來實現
  • 通常需要一定等級的硬件支持

2、常用的三種實現方法

  • 禁用中斷(僅限於單處理器)
  • 軟件方法(複雜)
  • 原子操作指令(但處理器或多處理器均可)—更常用

3、可選的實現內容

  • 有忙等待
  • 無忙等待

參考鏈接:https://www.bilibili.com/video/av6538245

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章