CMU 15-213 Introduction to Computer Systems學習筆記(21) Synchronization: Basic

實際上,有一類很重要的併發編程,這種併發編程模型叫做【fork】和【join】(分叉和會和),這個模型中,程序有一系列階段組成,在每個階段,都有多個工作線程和一個管理線程,程序會創建多個工作線程,然後每個工作線程解決該階段的某些問題,在程序中,你可以使用某個可以切分成多個部分你的數據結構,然後每個線程更新其該數據結構中的某個塊,但出於某種原因,管理線程必須等待所有的工作線程才能進入下一階段,這裏做的事叫做【join】,前面的叫做【fork】。管理線程等待所有的工作線程完成,通過調用pthread_join,因爲只有當所有的工作做完時,纔可以去做下一階段的工作,這個模型在科學計算等方面非常的重要,在一些領域你可能需要做一些仿真,比如仿真自然界的一些現象,

Threads review

我們看到了使用線程程序好的方面,使用線程的併發可以共享所有的全局變量,但是這種共享可能會產生意外的情況,所以我們需要一種機制,可以控制線程交錯的順序(進程調度的順序),只有這樣當我們共享數據結構的時候,纔不會發生壞事,好的,控制進程交錯的過程稱爲同步(synchronization),我們將學習線程同步的一些技術,使用它們可以編寫出正確的線程程序。

Shared Variables in Threaded C Programs

如果一個進程沒有共享任何數據結構,這些線程是獨立運行的,線程交錯如何都不會有問題,但是線程一旦共享資源,我們就要小心

我們要了解什麼是線程中的資源共享,答案並不是像【全局變量是共享的,棧變量是不共享的】這麼簡單,

比如有這個定義:變量x是被共享的如果多個線程引用了x的實例

Threads Memory Model

我們來看下線程的內存模型,概念模型和真實模型略有不同,從概念上來講,有多個線程在單個進程的上下文中運行,有些進程上下文是共享的,有些不是共享的,每個線程有自己獨立線程id,棧,棧指針,程序計數器,條件碼,通用目的的寄存器,然後線程共享剩下的進程上下文,進程上下文由內核維護,如虛擬內存系統中的數據結構,打開的文件描述符,信號處理程序等等。但不幸的是,實際系統不是嚴格按照這種模型實現的,雖然寄存器是獨立的,內核爲所有寄存器維護單獨的上下文,但是由於線程共享地址空間,所有線程可以訪問所有的棧,一個線程可以訪問另一個線程的棧,雖然從概念上來講這些棧是獨立的,但是實際情況並非如此。

Mapping Variable Instances to Memory

Shared Variable Analysis

volatile告訴了編譯器永遠不要把變量放在寄存器中,所以volatile修飾的變量總是從內存中讀取,修改後又存儲到內存中。volatile可以防止變量永久存儲再寄存器中,volatile修飾的變量可以被加載到寄存器中,但緊接着該變量的值要被寫回到內存。

 

Progress Graphs

現在的問題是我們如何保證安全的軌跡,也就是我們所說的同步,我們想以某種方式配置內核,使得內核永遠不會調度不安全的軌跡,那我們應該怎麼做呢,我們必須同步這些線程的執行,但從另外一個角度講,我們只需要保證對臨界區的互斥訪問。一旦一個線程開始執行臨界區的第一條指令,我們不希望它被另一個具有相同臨界區的線程打斷,例如,一個對應於某個全局變量的臨界區,不能被執行同樣的臨界區的另一個線程打斷,

Enforcing Mutual Exclusion

 

Using Semaphores for Mutual Exclusion

Semaphores

信號量是一個非負的全局整數,信號量作爲同步的變量,被兩個內核函數P和V操作,兩個系統調用叫做P和V,P和V對應於荷蘭語Proberen(測試)和Verhogen(增加),我們簡稱P和V,只需要瞭解兩個函數的作用就好,這兩個函數都以信號量作爲參數,並且P操作具有以下語義,如果s不爲0,將其減1並且立即返回,如果s=0,就掛起這個線程,知道s變爲非0,然後通過V操作重啓該線程,如果信號量爲0,P就會阻塞,線程就會被掛起,直到它被V操作重新啓動,然後重新啓動P操作,現在P操作可以減1並將控制權返回給調用者。

V操作只將s增加1,這裏的增加不像前面的cnt++,這裏的增加操作是原子的,因此永遠不會被打斷,然後在它增加s後,它會檢查是否有線程阻塞在P操作上,你可以認爲,內核維護了一個被P操作阻塞的線程隊列,並且在V操作增加信號量之後,它會檢查該隊列是否又被P操作阻塞的線程,這些線程在執行P操作時,信號量爲0,然後它以某種不確定的順序重新啓動其中一個線程,按照某種順序,所以你不能假設按照某種順序,內核使用某種選擇算法從隊列中選擇一個線程,然後內核重啓這個被P操作阻塞的線程,然後P操作可以將信號量減1。

這樣定義P和V操作的主要目的是,讓信號量獲得一個信號量不變性的屬性,信號量不變性,也就是對於一個信號量,只通過P和V操作對信號量進行操作的時候,信號量的值總是大於或者等於0。這個性質非常的有用,利用這個屬性可以實現對臨界區的互斥訪問,

C Semaphore Operations

Using Semaphores for Mutual Exclusion

使用信號量去修復課程Slides中的bug,基本的想法是初始化信號量爲1,根據定義,任何初始化爲1的信號量稱爲互斥鎖,因爲它用來提供互斥操作,這種叫法可以追溯到Dijkstra早期的論文。因此我們爲程序中的每個共享變量提供一個唯一的互斥鎖,即初始化值爲1的信號量,在我們這個程序中,我們只關心cnt這個共享變量,因此我們只創建一個稱爲mutex的互斥鎖,然後使用PV操作將臨界區操作cnt的部分保護起來,也就是,你先調用P,然後執行臨界區,然後再調用V。

現在講一下我們使用信號量時我們要用到的一些術語

首先是二進制信號量,它是一個信號量,其值始終爲0和1,然後是互斥鎖(互斥量),即用來互斥的二進制信號量,P操作稱爲互斥鎖加鎖,V操作稱爲互斥鎖釋放。如果進程持有互斥鎖,那麼意味着互斥鎖已經加鎖,但未被釋放,因此互斥量和二進制信號量始終初始化爲1,互斥鎖用於互斥,但還有一種信號量叫做counting semaphore,它可以用來對系統的事件計數,對於accounting semaphore,信號量的值通常大於1,好,下面我們使用互斥鎖修復程序。

Why Mutexes Work

現在看這一個進度圖,這個進度圖對於使用PV操作同步後的程序,所以我們在臨界區之前調用P,然後執行臨界區,然後我們調用V,現在,P會減少信號量,而V會增加信號量,因此如果你記錄了執行狀態空間中每個點的信號量值,你會得到這些信號量的值,原點的信號量值爲1,然後程序開始執行,在H(1)之後,信號量爲1,然後我們執行P操作,信號量減1,然後程序繼續執行。

PV操作創造了禁止區,禁止區對應於狀態空間中的,信號量爲-1的地方,因爲這些地方是不可到達的,根據P和V的定義,這些地方永遠無法到達,這樣就形成了一個包含不安全區域的禁止區了,這樣就可以提供對臨界區的互斥訪問。

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