多執行緒解決方案及效能

2021-08-27 05:55:48 字數 4274 閱讀 5174

程式操作的開銷: (取自《深入理解並行程式設計》表3.1)

操  作

開  銷(ns)

比  率

單週期指令

0.61.0

最好情況的cas

37.9

63.2

最好情況的鎖

65.6

109.3

單次快取未命中

139.5

232.5

cas快取未命中

306.0

510.0

光纖通訊

3,000

5000

全球通訊

130,000,000

216,000,000

臨界區

由同步機制保護的一段**. 其執行過程受到同步原語的約束. 例如如果一組臨界區受同乙個全域性鎖的保護, 那麼在同一時刻只有乙個臨界區能被執行. 如果某個執行緒正在這樣的乙個臨界區中執行, 那麼其他執行緒在執行臨界區之前必須等待第乙個執行緒執行完臨界區.

資料鎖

一種可擴充套件的鎖設計. 對於特定的資料結構, 他的每乙個例項都有自己的鎖. 如果每乙個執行緒使用資料結構的不同例項, 那麼這些執行緒可以在臨界區中併發執行. 其優勢是只要資料例項增長, 就能自動擴充套件到增加的cpu中.

**鎖

這是一種簡單的鎖設計, 它是一種全域性鎖, 用於保護一組臨界區. 這樣是允許還是拒絕乙個執行緒對這組臨界區的訪問, 是基於當前正在訪問這些臨界區的執行緒集合. 而不是基於執行緒想要訪問的那些資料. 基於**鎖的程式, 增加資料集的大小通常不會增加擴充套件性. 與之相對的是資料鎖.

互斥鎖

互斥鎖是一種互斥機制. 在同一時刻, 他僅僅允許乙個執行緒進入被鎖保護的臨界區執行.

無效化

當乙個cpu想要寫入資料, 它必須首先確保該資料沒有存在於其他cpu快取中. 如果有必要, 需要從寫資料的cpu向擁有該資料的cpu傳送invalidation(使無效)訊息

rcu (read-copy-update)

一種同步機制, 可以認為是讀寫鎖和引用計數的替代品. rcu提供了極低的讀端負載. 為了讓已經存在的讀端獲得其優勢, 寫端為了已有讀端的利益, 需要額外的負載來維護讀端的舊版本資料. 讀端既不阻塞也不自旋, 因此不會形成死鎖. 而且它們也會看到過時的資料, 並能與寫端併發執行. 因此, rcu非常適合用在大量讀的情形, 其過時的資料可以被容忍或避免.

讀臨界區

被讀寫同步機制的讀鎖所保護的**片段. 例如, 如果乙個臨界區被某個全域性讀寫鎖的讀鎖保護, 另乙個臨界區被同乙個讀寫鎖的寫鎖保護, 那麼第乙個臨界區就是該鎖的讀臨界區. 任意數量的執行緒可以併發的在讀臨界區中執行. 但是僅僅在沒有寫臨界區執行時, 讀臨界區才能併發執行.(不能發生寫操作)

寫臨界區

被讀寫同步機制的讀鎖所保護的**片段. 例如, 如果乙個臨界區被某個全域性讀寫鎖的寫鎖保護, 另乙個臨界區被同乙個讀寫鎖的讀鎖保護, 那麼第乙個臨界區就是該鎖的寫臨界區. 在同一時刻, 只能有乙個執行緒執行在寫臨界區中, 並且此時不能有任何執行緒執行在讀臨界區中.

設計模式與鎖粒度

資料所有權 -釋 放-> 資料幀 -批處理-> **鎖 -批處理-> 單執行緒程式

資料所有權 <-分 割- 資料幀 <-分 割- **鎖 <-擁  有- 單執行緒程式

synchronized **鎖

synchronized是乙個記憶體屏障. 限制編譯器和cpu對指令亂序執行的優化.

synchronized關鍵字的效能: 在1.8之後, synchronized經過了優化, 在競爭激烈時效能差, 但是比cas效率更高.

cas操作

針對單一變數, 在適用場景的效率很高, 但是可擴充套件性差. 鎖競爭激烈時耗費時間長, 大量的失敗重試和自旋消耗效能. cas需要有2個原子操作.

原子計數的效能隨執行緒的增加而降低.

原因: 在多cpu的核心中, 為了讓所有cpu都能計數, 包含變數的快取行在所有cpu間傳播, 通過兩兄弟cpu的互聯模組和系統互聯模組傳輸. 這種傳播非常耗時.  --> 資料所有權. 每個cpu只更新自己的計數 把所有的執行緒計數相加就得到了總的計數.

一種方法是使用陣列, 陣列的每個元素代表乙個cpu, 每個cpu可以快速的增加自己執行緒的變數值. 就不再需要代價高昂的跨越整個計算機系統的通訊.

然而這種在更新端擴充套件性極佳的方法, 在存在大量的執行緒時, 會帶來巨大的讀取端的執行時間.

以犧牲資料的及時性獲取在讀取端的高擴充套件. 專門有乙個執行緒負責把每執行緒計數傳給全域性計數, 讀者只讀取全域性計數. 一旦寫者更新完畢, 計數結果總是正確的. 這就是最終結果一致性.

lock ( reentrantlock 讀寫鎖 )

讀寫鎖允許任意數量的執行緒, 但是僅僅允許乙個寫執行緒進入由鎖保護的臨界區. 在讀寫鎖保護的臨界區中, 只要有乙個寫執行緒, 就將阻止其他所有的讀執行緒進入臨界區, 反之亦然.

讀寫鎖的的最關鍵之處在於公平性. 不停止的讀者將餓死寫者, 反之亦然.

在寫操作多時效能很差. 只適合用在寫次數不多的情況.

並行快速路徑

並行快速路徑是一種思路, 它的含義是在多數情況沒有執行緒間通訊和互動的開銷, 而偶爾進行的跨程序通訊又使用了精心設計的 ( 開銷仍然很大 ) 全域性演算法. 例如最大執行緒計數: 總數不能近似超過某個值.

給每個執行緒乙個每執行緒計數和max計數. 當執行緒計數達到max計數時, 把當前計數的一半分給總計數. 這樣只在計數max/2的次數後才需要加一次鎖, 大大降低了鎖的競爭程度. 如果要更精確些, 就把max設的值小一點.

例如資源分配器快取:

rcu保護

見定義. 在允許過時資料的情況下具有良好的擴充套件性.並且可以採取措施消除過時資料

分割雙端佇列進行併發操作的鎖實現

雜湊雙端佇列: 雜湊永遠是分割乙個資料結構的最簡單和最有效的方法.

雙端佇列的實現是 」垂直並行化」 或者 」管道」 的極佳例子.

層次鎖在獲取一把粗粒度的鎖同時, 再獲取一把細粒度的鎖. 但是我們花費了第二次獲取鎖的開銷, 僅僅持有很短的時間. 在這種情況下, 簡單的資料鎖效能更好.

鎖的層次是指為鎖逐個編號, 禁止不按順序獲取鎖. 這樣當執行緒已經獲取了編號相同或更高的鎖, 就不允許再次獲取編號相同或編號更低的鎖.

層次鎖的庫函式: 幸運的是, 庫函式一般不需要呼叫使用者的**, 因此庫函式獲取了自己的鎖, 它絕不會再去獲取呼叫者的鎖, 這樣避免出現庫函式和呼叫者之間互相持鎖的死迴圈. 如果萬一某個庫函式呼叫了呼叫者的**, 比如qsort()呼叫了乙個呼叫者提供的比較函式, 恰恰這個qsort()比較複雜也 使用了鎖, 那麼就可能出現死鎖. 出現這種情況的**定律是: 在呼叫未知**前釋放所有鎖. qsort()函式在呼叫比較函式前釋放它持有的全部鎖.

如果每個模組都在呼叫未知的方法之前都釋放全部的鎖, 那麼每個模組自身都避免了死鎖.

資料所有權

資料所有權方法按照執行緒或者cpu個數分割資料結構. 在不需要任何同步開銷的情況下,每個執行緒或者cpu都可以訪問屬於它的子集.但是如果執行緒a想訪問執行緒b的資料, 必須通過執行緒間通訊, 或者把資料遷移到執行緒a上來.

資源分配器快取

解決在大多數情況下快速的分配和釋放記憶體, 在特殊的情況下高效的分配和釋放記憶體之間的矛盾.

對鎖友好的資料結構

可分割的map集合

順序鎖順序鎖主要用於保護以讀取為主的資料. 多個讀者觀察到的狀態必須一致. 不像讀寫鎖, 順序鎖不能阻塞寫者. 它反而更像危險指標, 如果檢測到有併發的寫者, 順序鎖強制讀者重試. 使用順序鎖的時候, 設計很重要, 盡量不要讓讀者有重試的機會.

實現的關鍵是序列號. 沒有寫者時為偶數值, 如果乙個更新正在進行中, 序列號是奇數值. 讀者在每次訪問之前和訪問之後把序列號對照. 如果序列號是奇數, 或者兩個快照的值不同, 則存在併發更新. 此時讀者必須丟棄全部的資料重試.

危險指標

反向實現引用計數, 而不是增加儲存在資料元素中的某個整數. 在每執行緒鍊錶中儲存指向該元素的指標, 這個煉表裡的元素被稱為危險指標. 每個資料元素有乙個」虛擬引用計數」, 其值可以通過計算有多少個危險指標指向該元素得到. 因此, 如果該元素已被標記為不可訪問, 並且不可以有任何引用它的危險指標, 該元素就可以被安全的釋放.

監視器生產者和消費者問題. 可以減少鎖競爭

通道生產者 - 消費者模式可以提供高效的資料通訊, 而不依賴於訊號量, 互斥量或監視器來進行資料傳輸. 在單個生產者和單個消費者的情況下, 使用通道不必產生端對端的原子開銷.

避免了對共享變數的原子操作(資料所有權)

不適用於多個生產者和消費者

避免了執行緒陷入阻塞. 取消了額外的同步開銷

陣列:每執行緒變數:

新增一致性的延遲處理:

使用訊號代替原子變數可以減輕效能損失.

並行化只是優化的一種. 如果最後發現效能沒有明顯提公升, 不建議使用並行方案. 需要重新考慮方案的設計, 進行更多優化.

python多執行緒死鎖及解決方案

所謂死鎖 是指兩個或兩個以上的程序或執行緒在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序 儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。產生死鎖的 import threadi...

多執行緒併發同步問題及解決方案

執行緒同步其核心就在乙個同步,即協同 配合,也就是按照預定順序先後的執行,也就是你做我等,你做完並返回,然後我開始執行。執行緒同步就是當執行緒發出乙個功能呼叫時,在沒得到結果前不會返回,且其他執行緒不能呼叫該方法。在多執行緒裡面有些敏感資料不允許被多個執行緒同時訪問,使用執行緒同步就是要保證在乙個時...

C 多執行緒物件導向解決方案

相信很多人都讀過 c 沉思錄 這本經典著作,在我艱難地讀完整本書後,留給我印象最深的只有一句話 用類表示概念,用類解決問題 關於多執行緒程式設計,如果不是特別需要,大多數開發人員都不會特意去觸碰這個似乎神秘的領域。如果在某些場合能正確並靈活地運用,多執行緒帶來的好處是不言而喻的。然而,任何事物都有兩...