操作系統精髓與設計原理學習筆記五:併發性(互斥和同步)

併發是所有問題的基礎,也是操作系統設計的基礎。併發包括很多設計問題,其中有進程間通信資源共享與競爭,多個進程活動的同步以及分配給進程的處理器時間等。

支持併發進程的雞巴需求是加強互斥能力。也就是說,當一個進程被授予互斥能力時,那麼在其活動期間,它具有排斥所有其他進程的能力。

主要的三種方法:信號量,管程,消息傳遞。


零、與併發相關的關鍵術語

1、原子操作:一個函數貨動作由一個或多個指令的序列實現,對外是不可見的;也就是說,沒有其他進程可以看到其中間狀態貨中斷此操作。要保證指令的序列要麼作爲一個組來執行,要麼都不執行。原子性保證了併發進程的隔離。

2、臨界區:是一段代碼,在這段代碼中進程將訪問共享資源,當另外一個進程已經在這段代碼中運行時,這個進程就不能在這段代碼中執行。

3、死鎖:兩個或兩個以上的進程因其中的每個進程都在等待其他進程做完某些事情而不能繼續執行。

4、互斥:當一個進程在臨界區訪問共享資源時,其他進程不能進入該臨界區訪問任何共享資源。

5、競爭條件:多個線程貨進程在讀寫一個共享數據時,結果依賴於他們執行的相對時間,這種情形稱爲競爭條件。

6、飢餓:是指一個可運行的進程儘管能繼續執行,但被調度程序無限期的忽視,而不能被調度執行的情形。


一、併發的原理

在單處理器多道程序設計系統中,進程被交替執行,表現出一種併發執行的外部特徵。在多處理器系統中,不僅可以交替執行進程,而且可以重疊執行進程。

涉及的困難:1)全局資源的共享充滿了危險;2)操作系統很難對資源進行優化分配。一個多處理器系統還必須處理多個進程同時執行所引發的問題。

1、一個例子

共享變量的問題例子:問題的本質在於共享全局變量。多個進程訪問這個全局變量,如果一個進程修改了它,然後被中斷,另一個進程可能在第一個進程使用它的值之前又修改了這個變量。在單處理器系統的情況下,出現問題的原因是中斷可能會在進程中的任何地方停止指令的執行;在多處理器系統的情況下,不僅同樣的條件可以引發問題,而且當兩個進程同事執行並且都試圖訪問同一個全局變量時,也會引發問題。

解決思路:控制對共享變量的訪問。上面的例子說明,如果需要保護共享的全局變量(及其他共享的全局資源),唯一的辦法是控制訪問該變量的代碼。如果我們定義了一條規則,一次只允許一個進程進入echo,並且只有在echo過程允許結束後,他纔對另一個進程是可用的,那麼剛纔討論的那類問題就不會發生了。

2、競爭條件

競爭條件發生在多個進程或線程讀寫數據時,其最終的結果依賴於多個進程的指令執行順序。

3、操作系統關注的問題

1)操作系統必須能夠跟蹤不同的進程;

2)操作系統必須爲每個活躍的進程分配和釋放各種資源;

3)操作系統必須包含每個進程的數據和物理資源,避免其他進程的無意干涉;

4)一個進程的功能和輸出結果必須與其相對於其他併發進程的執行速度無關。

4、進程的交互

三種方式:進程之間相互不知道對方的存在(競爭情況);進程通過共享某些對象的方式間接知道對方的存在;進程之間知道對方的存在(這些進程可以通過進程ID互相通信)。

1)進程間的資源競爭

當併發進程競爭使用同一組員時,他們之間會發生衝突。競爭進程間沒有任何信息交換,但是一個進程的執行可能會影響到競爭進程的行爲。

競爭進程面臨的三個控制問題:a)互斥的要求。假設兩個或更多的進程需要訪問一個不可共享的資源,我們把這類資源稱爲臨界資源,使用臨界資源的那一部分程序稱爲程序的臨界區。一次只允許有一個程序在臨界區中,這一點非常重要。b)死鎖 。c)飢餓。

由於操作系統負責分配資源,競爭的控制不可避免的設計操作系統。此外,進程自身需要能夠以某種方式表達互斥的要求,比如在使用前對資源加鎖,但任何一種方案都設計到操作系統的某些支持,如提供鎖機制。

2)進程間通過共享的合作

通過共享進行合作的情況,包括進程間在互相併不確切知道對方的情況下進行交互。多個進程可能訪問一個共享變量、共享文件或數據庫,進程可能使用並修改共享變量而並不涉及其他進程,但卻知道其他進程也可能訪問同一個數據。因此,這些進程必須合作,以確保它們共享的數據得到正確管理。

由於數據保存在資源中,因此再次涉及有關互斥、死鎖和飢餓等控制問題。唯一的卻別是可以以兩種不同的模式(讀和寫)訪問數據項,並且只有寫操作必須保持互斥。

對於數據一致性的要求,在通過共享進行合作的情況下,臨界區的概念是非常重要的。

3)進程間通過通信的合作

在競爭的情況下,進程在不知道其他進程存在的情況下共享資源

在共享合作的情況下,進程共享變量,每個進程並未明確的知道其他進程的存在,它只知道需要維護數據的完整性。

當進程通過通信進行合作時,哥哥進程都與其他進程直接進行連接,通信提供了同步和協調各種活動的方法。在典型情況下,通信科可由各種類型的消息組成,發送消息和接受消息的原語由程序設計語言提供或者由操作系統的內核提供

5 實現互斥的幾種方法

1)軟件方法:讓由併發執行的進程擔負實現互斥的責任,這類進程,不論是系統程序還是應用程序,都需要與另一個進程合作,二不需要程序設計語言或操作系統通過任何支持來實施互斥。

2)第二種方法涉及專門的機器指令。

3)在操作系統貨程序設計語言中提供某種級別的支持。


二、互斥-硬件的支持

1 中斷禁用

在單處理器機器中,併發進程不能重疊,只能交替。此外,一個進程將一直運行,直到它調用了一個系統服務或被中斷。因此爲保證互斥,只需要保證一個進程不被中斷就可以了,這種能力可以通過系統內核爲啓用和禁用中斷定義的原語來提供。但該方法的代價非常高,而且不能用於多處理器結構。

2專用機器指令


三、信號量

信號量:用於進程間傳遞信號的一個整數值。在信號量上只有三種操作可以進行:初始化、遞減和增加。這三種操作都是原子操作。遞減操作可以用於阻塞一個進程,增加操作可以用於解除阻塞一個進程。

兩個或多個進程可以通過簡單的信號進行合作,一個進程可以被迫在某一位置停止,直到它接受到一個特定的信號。任何複雜的合作需求都可以通過適當的信號結構得到滿足。爲了發信號,需要使用一個稱爲信號量的特殊變量。爲通過信號量s傳送信號,進程可執行原語semSignal(s);爲通過信號量s接收信號,進程可執行原語semWait(s)。如果相應的信號仍沒有發送,則進程被阻塞,直到發送完爲止。

可以把信號量視爲一個具有整數值的變量,在它之上定義三個操作:

1)一個信號量可以初始化成非負數;

2)semWait操作使信號量減1。若值爲負數,則執行semWait的進程被阻塞。否則進程繼續執行。

3)semSignal操作使信號量加1.若值小於或等於零,則被semWait操作阻塞的進程被解除阻塞。

對這三種草垛的解釋如下:開始時,信號量的值爲零或正數。如果該值爲正數,則該值等於發出semWait操作後可立即繼續執行的進程的數量。如果該值爲零,則發出semWait操作的下一個進程會被阻塞,此時該信號量的值變爲負值。之後,每個後續的semWait操作都會使信號量的負值更大。該負值等於正在等待解除阻塞的進程的數量。在信號量爲負值的情形下,每一個semSignal操作都會將等待進程中的一個進程解除阻塞。。

信號量需要使用隊列來保存在信號量上等待的進程。最公平的策略是先進先出,被阻塞時間最久的進程最先從隊列釋放(強信號量,保證不會出現飢餓);沒有規定進程從隊列中移出順序的信號量稱爲弱信號量

1 互斥

semWait(s)

/*臨界區*/

semSignal(s)

/*其他部分*/

信號量一般初始化爲1,這樣第一個執行semWait的進程可以立即進入臨界區,並把s的值置爲0。

接着任何試圖進入臨界區的其他進程,豆漿發現第一個進程忙(semWait後s<0),因此被阻塞,把s的值減1。

當最初進入臨界區的進程離開時,s增1,一個被阻塞的進程(如果有的話)被移出等待隊列,置於就緒態,這樣,當操作系統下一次調度時,它可以進入臨界區。

2 生產者、消費者問題

有一個活多個生產者生產某種類型的數據(記錄、字符),並放置在緩衝區中;有一個消費者從緩衝區中取數據,每次取一項;系統保證避免對緩衝區的重複操作,也就是說,在任何時候只有一個主體(生產者或消費者)可以訪問緩衝區。問題是要確保這種情況,當緩存已滿時,生產者不會繼續向其中添加數據;當緩存爲空時,消費者不會從中移走數據。

需要三個信號量empty和full用於同步緩衝區,而mut變量用於在訪問緩衝區時是互斥的。

 private static Mutex mut = new Mutex();//臨界區信號量
 private static Semaphore empty = new Semaphore(5, 5);//只有佔用該信號量時生產者才能生產,表示有緩衝區中有空閒位置,初始時全都是空閒的
 private static Semaphore full = new Semaphore(0, 5);//只有佔用該信號量時消費者才能消費,表示緩衝區中有數據,初始時一個數據都沒有

  private static void Producer()
         {
             Console.WriteLine("{0}已經啓動",Thread.CurrentThread.Name);
             empty.WaitOne();//對empty進行P操作
             mut.WaitOne();//對mut進行P操作
             Console.WriteLine("{0}放入數據到臨界區", Thread.CurrentThread.Name);
                 Thread.Sleep(1000);
             mut.ReleaseMutex();//對mut進行V操作
             full.Release();//對full進行V操作
         }

         private static void Customer()
         {
             Console.WriteLine("{0}已經啓動", Thread.CurrentThread.Name);
             Thread.Sleep(12000);
             full.WaitOne();//對full進行P操作
             mut.WaitOne();//對mut進行P操作
             Console.WriteLine("{0}讀取臨界區", Thread.CurrentThread.Name);
             mut.ReleaseMutex();//對mut進行V操作
             empty.Release();//對empty進行V操作
         }

3 信號量的實現

問題的本質是互斥,任何時候只有一個進程可以用semWait或semSignal操作控制一個信號量。

對於但處理器而系統,在semWait或semSignal操作期間是可以禁用中斷的。

semWait(s){

  禁用中斷

  s.count--;

  if(s.count<0){

   該進程進入s.queue隊列

   阻塞該進程,並允許中斷

  }

  else

    允許中斷

}

semSignal(s){

  禁用中斷;

  s.count++;

  if(s.count<=0){

    從s.queu隊列中移出進程P

    進程P進入就緒隊列

  }

}


四、管程

管程是由一個或多個過程、一個初始化序列和局部數據組成的軟件模塊,其主要特點如下:

1)局部數據變量只能被管程的過程訪問,任何外部過程都不能訪問;

2)一個進程通過調用管程的一個過程進入管程;

3)在任何時候,只有一個進程在管程中執行,調用管程的任何其他進程都被阻塞,以等待管程可用。

管程可以提供一種互斥機制:管程中的數據變量每次只能被一個進程訪問到。因此,可以把一個共享數據結構放在管程中,從而提供對它的保護。如果管程中的數據代表某些資源,那麼管程爲訪問這些資源提供了互斥機制。

管程通過使用條件變量(一種數據類型,用於阻塞進程或現場,直到特定的條件爲真)提供對同步的支持,這些條件變量包含在管程中,並且只有在管程中才能被訪問。有兩個函數可以操作條件變量:

1)cwait(c):調用進程的執行在條件c上阻塞,管程現在可以被另一個進程調用。

2)csignal(c):恢復執行在cwait之後因爲某些條件而阻塞的進程。如果有多個這樣的進程,選擇其中一個;如果沒有這樣的進程,什麼都不做。

當一個進程在管程中時,他可能通過發送cwait(x)把自己暫時阻塞在條件x上,隨後他被放入等待條件改變以重新進入管程的進程對了中,在cwait(x)調用的嚇一跳指令開始恢復執行。如果在管程中執行的一個進程發現條件變量x發生了變化,它將發送csignal(x),通知相應的條件隊列條件已經改變。

與信號量相比,管程擔負的責任不同。對於管程,它構造了自己的互斥機制:生產者和消費者不可能同時訪問緩衝區;但是程序猿必須把適當的cwait和csignal原語放在管程中,用於防止進程往一個滿緩衝區中存放數據項,或者從一個空緩衝區中取數據項(同步)。而在使用信號量的情況下,執行互斥和同步都屬於程序猿的責任。


五、消息傳遞

進程交互時,必須滿足兩個基本要求:同步和通信。爲實施互斥,進程間需要同步;爲了合作,進程間需要交換信息,提供這些功能的一種方法是消息傳遞。消息傳遞可以再分佈式系統,共享內存的多處理器系統和單處理器系統中實現。

消息傳遞的實際功能以一對原語的形式提供:

send(destination,message)

receive(source,message)

這是進程間進行消息傳遞所需要的最小操作集。一個進程以消息的形式給另一個指定的目標進程發送信息;進程通過執行receive原語接受信息,receive原語中指明發送消息的源進程和消息

1 同步

兩個進程間的消息通信隱含着某種同步信息:只有當一個進程發送消息後,接受者才能接受消息。發送者和接受者都可以阻塞或不阻塞,通常有三個組合:

1)阻塞send,阻塞receive

2)無阻塞send,阻塞receive

3)無阻塞send,無阻塞receive

2 尋址

在send和receive原語中確定目標或源進程的方案可分爲兩類:直接尋址和間接尋址。

對於直接尋址,send原語包含目標進程的標識號,而receive原語有兩種處理方式。一種是要求進程顯示的指定源進程,因此,該進程必須事先知道希望得到來自哪個進程的信息(這種方式對於處理併發進程間的合作是非常有效的);另一種情況是不可能指定所期望的源進程。

對於間接尋址,通過解除發送者和接受者之間的耦合關係,在消息的使用上允許更大的靈活性。在這種情況下,消息不是知己從發送者發送到接受者,而是發送到一個共享數據結構,該結構由臨時保存消息的隊列組成,這些隊列通常稱爲信箱。因此,對兩個通信進程,一個進程給合適的信箱發送消息,另一個進程從信箱中獲得這些消息。

3 互斥

希望進入臨界區的進程首先試圖接收一條消息,如果信箱爲空,則該進程被阻塞;一旦進程獲得消息,它執行它的臨界區,然後把該消息放回信箱。因此,消息函數可以視爲在進程之間傳遞的一個令牌。

1)如果有一條消息,它僅僅被傳遞給一個進程,其他進程被阻塞;

2)如果消息隊列爲空,那麼所有進程被阻塞;當有一條消息可用時,只有一個阻塞進程被激活並得到這條消息。


六、讀者寫者問題

問題描述:

1)任意多的讀進程可以同時讀這個文件;

2)一次只有一個寫進程可以寫文件;

3)如果一個寫進程正在寫文件,那麼禁止任何讀進程讀文件。

1 讀者優先

2 寫者優先


七、小結

現代操作系統的核心是多道程序設計、多處理器和分佈式處理器。這些方案的基礎及操作系統設計技術的基礎是併發。當多個進程併發執行時,不論是在多處理器系統的情況下,還是在單處理器多道程序系統中,都會產生衝突和合作的問題。

互斥指的是,對一組併發進程,一次只有一個進程能夠訪問給定的資源或執行給定的功能。互斥技術可以用於解決諸如資源徵用之類的衝突,還可以用於進程間的同步,使得它們可以合作。

支持互斥的另一種方法是在操作系統中提供相應功能,其中最常見的兩種技術是信號量消息機制。信號量用於在進程間發信號,並可以很容易的實施一個互斥協議;消息對實施互斥是很有用的,它還爲進程間的通信提供了一種有效的方法。

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