併發程式設計

劉小緒同學發表於2019-02-27

    在異常控制流提過,如果邏輯控制流在時間上是重疊的,那麼它們就是併發的。併發出現在計算機不同層面上,編寫併發程式也是程式設計師必不可少的技能,面試同樣必問併發相關知識。

    現代作業系統提供了基於三種基本的構造併發程式的方法。分別為:程式、I/O 多路複用和執行緒。

    基於程式的併發程式設計方法很簡單,使用我們很熟悉的forkexecwaitpid等函式就可以了。比如構造一個併發伺服器的方法就是,在父程式中接受客戶端的請求,然後建立一個新的子程式來為每個客戶端提供服務。

併發程式設計


併發程式設計


併發程式設計


併發程式設計

    因為程式有獨立的地址空間,所以不會出現程式不小心覆蓋另一個程式虛擬記憶體的情況,這是一個顯著的優點。但獨立地址空間也讓不同程式之間共享資訊變得更加困難。

    考慮下面一種場景,伺服器也能對使用者從標準輸入鍵入的互動命令做出響應,那伺服器就必須響應兩個相互獨立的 I/O 事件:1)客戶端連線請求,2)使用者鍵入命令列。我們無法選擇應該等待哪個事件,因為等待其中一個就不能響應另一個事件,所以就有了 I/O 多路技術。

    其基本思路就是使用select函式,要求核心掛起程式,只有在一個或多個 I/O 事件發生後,才將控制返回給應用程式。

    該技術可以用作併發事件驅動程式的基礎,在事件驅動程式中某些事件會導致流向前推進(比如 web 前端程式),一般思路是將邏輯流轉換為狀態機(去查編譯原理),某些事件會導致從一個狀態轉移到另一個狀態。

    如果使用程式併發實現上面的場景,那可能就需要兩個程式分別監聽兩個事件,而使用 I/O 多路複用技術的程式時執行在單一程式上下文中,因此每個邏輯流都能訪問全部該程式全部的地址空間,也就是說共享資料變得很容易,但是事件驅動要比基於程式更加複雜,而且隨著併發粒度的減小,複雜性還會上升。另外如果某個邏輯流正忙於讀一個文字行,那麼其它邏輯流就不會有進展,這防不住惡意客戶端的攻擊。

    執行緒同時結合了前面兩種方法。執行緒是執行在程式上下文的邏輯流,它有自己的棧、棧指標、程式計數器等,但是共享這個程式虛擬空間的所有內容,包括堆、程式碼、資料、共享庫及開啟檔案。

    下面是多執行緒執行模型,每個程式開始生命週期時都是單一執行緒,稱之為主執行緒,在某一時刻主執行緒建立一個對等執行緒,從這個時間點開始,兩個執行緒就併發地執行。

併發程式設計

    併發程式最大的問題就是同步,我們使用進度圖來表示。將 n 個併發執行緒的執行模型化一條 n 維笛卡爾空間中的軌跡線,每條軸 k 對應執行緒 k 的進度,每個點表示執行緒 k 已經完成了指令 Ik這一狀態,比如一個程式中有兩個執行緒,它的執行歷史如下,我們就可以將其模型化為狀態空間的一條軌跡線。

H1,L1,U1,H2,L2,S1,T1,U2,S2,T2

併發程式設計

    那麼對於執行緒 i,操作共享變數cnt內容的指令(,Li,Ui,Ui)構成一個臨界區,那麼就能分析出不安全區域,如果軌跡流經過不安全區域,那程式就有可能出錯。

併發程式設計

    當然,為了解決這一問題,荷蘭科學家迪傑斯特拉提出了訊號量機制,解決同步不同執行執行緒問題,就是我們很熟悉的 P、V 操作,使用訊號量來實現互斥,就好像在上面的不安全區域加了一道牆壁,軌跡線無法進入這個不安全區域。

    訊號量也用來排程共享資源,比如經典的生產者-消費者、讀者-寫者的問題。需要注意的是,訊號量又引入了一種潛在的執行時錯誤,那就是死鎖,這一部分知識查詢容易,就不介紹了。code

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555494/viewspace-2637113/,如需轉載,請註明出處,否則將追究法律責任。

相關文章