天天看點

31、深入了解計算機系統筆記,并發程式設計(concurrent)(3)

1、基于預線程化(prethreading)的并發伺服器

正常的并發伺服器中,我們為每一個用戶端建立一個新線程,代價較大。一個基于預線程化的伺服器通過使用“生産者-消費者模型”來試圖降低這種開銷。

31、深入了解計算機系統筆記,并發程式設計(concurrent)(3)

伺服器由一個主線程和一組worker線程組成的,主線程不斷地接受來自用戶端的連接配接請求,并将得到的連接配接描述符放在一個共享的緩沖區中。每一個worker線程反複從共享緩沖區中取出描述符,為用戶端服務,然後等待下一個描述符。

示例代碼

    如上模型組成事件驅動伺服器,事件驅動程式建立它們自己的并發邏輯流,這些邏輯流被模型化為狀态機,帶有主線程和worker線程的簡單狀态機。

2、其他并發問題

一個函數被稱為線程安全(thread-safe)的,當且僅當多個線程反複地調用時,它會一下産生正确的結果。

下面是四類不安全(相交)的函數:

1)不保護共享變量的函數

利用P,V操作解決這個問題。

2)保持跨越多個調用的狀态的函數

示例代碼1

srand設定種子,調用rand生成随機數。多線程調用時就出問題了。我們可以重寫之解決,使之不再使用任何靜态資料,取而代之地依靠調用者在參數中傳遞狀态資訊。

示例代碼2

3)傳回指向靜态變量的指針的函數

某些函數(如gethostbyname)将結果放在靜态結構中,并傳回一個指向這個結構的指針。多線程并發可能引發災難,因為正在被一個線程使用的結果會被另一個線程悄悄覆寫。

兩種方法處理:

一是重寫之。使得調用者傳遞存放結果的結構的位址,這就消除了共享資料。

第二種方法是:使用稱為lock-and-copy的技術。在每一個調用位置,對互斥鎖加鎖,調用線程不安全函數,動态地為結果配置設定存儲器,copy函數傳回結果到這個存儲器位置,對互斥鎖解鎖。

示例代碼3

4)調用線程不安全函數的函數

    f調用g。如果g是2)類函數,則f也是不安全的,隻能得寫。如果g是1)或3)類函數,則利用互斥鎖保護調用位置和任何想得到的共享資料,f仍是線程安全的。如上例中。

3、可重入性

可重入函數(reenterant function)具有這樣的屬性:當它們被多個線程調用時,不會引用任何共享資料。

31、深入了解計算機系統筆記,并發程式設計(concurrent)(3)

可重入函數通常比不可重入函數高效一些,因為不需要同步操作。

如果所有的函數參數都是傳值傳遞(沒有指針),且所有的資料引用都是本地的自動棧變量(沒有引用靜态或全局變量),則函數是顯式可重入的,無論如何調用,都沒有問題。

允許顯式可重入函數中部分參數用指針傳遞,則隐式可重入的。在調用線程時小心傳遞指向非共享資料的指針,它才是可重入。如rand_r。

可重入性同時是調用者和被調用者的屬性。

4、C庫中常用的線程不安全函數及unix線程安全版本

31、深入了解計算機系統筆記,并發程式設計(concurrent)(3)

5、競争

    當一個程式的正确性依賴于一個線程要在另一個線程到達y點之前到達它的控制流中的x點時,就會發生競争(race)。

示例代碼2(消除競争)

6、死鎖

信号量引入一個潛在的運作是錯誤-死鎖。死鎖是因為每個線程都在等待其他線程運作一個根本不可能發生的V操作。

避免死鎖是很困難的。當使用二進制信号量來實作互斥時,可以用如下規則避免:

如果用于程式中每對互斥鎖(s,t),每個既包含s也包含t的線程都按照相同順序同時對它們加鎖,則程式是無死鎖的。

參考

繼續閱讀