架構設計:生產者/消費者模式[4]:雙緩衝區
架構設計:生產者/消費者模式[4]:雙緩衝區
文章目錄
★為啥要雙緩衝區?
★雙緩衝區的原理
★雙緩衝區的幾種狀態
★可能的併發問題
★應用場景
“雙緩衝區”是一個應用很廣的手法。該手法用得最多的地方想必是螢幕繪製相關的領域(主要是為了減少螢幕閃爍)。另外,在裝置驅動和工控方面,雙緩衝也經常被使用。不過今天要聊的,並不是針對上述的某個具體領域,而是側重於併發方面的同步/互斥開銷。另外提醒一下,雙緩衝方式和前面提到的佇列緩衝、環形緩衝是可以結合使用滴。
★為啥要雙緩衝區?
記得前幾天在介紹佇列緩衝區時,提及了普通佇列緩衝區的兩個效能問題:“記憶體分配的開銷”和“同步/互斥的開銷”(健忘的同學,先回去看看那個帖子複習一下)。“記憶體分配的開銷”已經在介紹環形緩衝區的時候解決了,而今天要介紹的雙緩衝區,就是衝著同步/互斥的開銷來的。
為了防止有人給我們扣上“過度設計”的大帽子,又得來一個事先宣告:只有當同步或互斥的開銷非常明顯的時候,你才應該考慮雙緩衝區的使用。否則的話,大夥兒還是老老實實用最基本、最簡單的佇列緩衝區吧。
★雙緩衝區的原理
前面說了一通廢話,現在開始切入正題,說說具體實現。
所謂“雙緩衝區”,故名思義就是要有倆緩衝區(簡稱 A 和 B)。這倆緩衝區,總是一個用於生產者,另一個用於消費者。當倆緩衝區都操作完,再進行一次切換(先前被生產者寫入的轉為消費者讀出,先前消費者讀取的轉為生產者寫入)。由於生產者和消費者不會同時操作同一個緩衝區(不發生衝突),所以就不需要在讀寫每一個資料單元的時候都進行同步/互斥操作。(順便提一下,這又一次展現了【空間換時間】的優化思路)
但是光有倆緩衝區還不夠。為了真正做到“不衝突”,還得再搞兩個互斥鎖(簡稱 La 和 Lb),分別對應倆緩衝區。生產者或消費者如果要操作某個緩衝區,必須先擁有對應的互斥鎖。補充一句:要達到“不衝突”的效果,其實可以有多種搞法,今天只是挑一個簡單的來聊。
★雙緩衝區的幾種狀態
為了加深某些同學的理解,再描述一下雙緩衝區的幾種狀態。
◇倆緩衝區都在使用的狀態(併發讀寫)
大多數情況下,生產者和消費者都處於併發讀寫狀態。不妨設生產者寫入 A,消費者讀取 B。在這種狀態下,生產者擁有鎖 La;同樣的,消費者擁有鎖 Lb。由於倆緩衝區都是處於獨佔狀態,因此每次讀寫緩衝區中的元素(資料單元)都【不需要】再進行加鎖、解鎖操作。這是節約開銷的主要來源。
◇單個緩衝區空閒的狀態
由於兩個併發實體的速度會有差異,必然會出現一個緩衝區已經操作完,而另一個尚未操作完。不妨假設生產者快於消費者。
在這種情況下,當生產者把 A 寫滿的時候,生產者要先釋放 La(表示它已經不再操作 A),然後嘗試獲取 Lb。由於 B 還沒有被讀空,Lb 還被消費者持有,所以生產者進入發呆(Suspend)狀態。
◇緩衝區的切換
接著上面的話題。
過了若干時間,消費者終於把 B 讀完。這時候,消費者也要先釋放 Lb,然後嘗試獲取 La。由於 La 剛才已經被生產者釋放,所以消費者能立即擁有 La 並開始讀取 A 的資料。而由於 Lb 被消費者釋放,所以剛才發呆的生產者會緩過神來(Resume)並擁有 Lb,然後生產者繼續往 B 寫入資料。
經過上述幾個步驟,倆緩衝區完成了對調,變為:生產者寫入 B,消費者讀取 A。
★可能的併發問題
本來單個緩衝區的生產者/消費者問題就已經是教科書的經典問題了,現在搞出倆緩衝區,所以就更加耗費腦細胞了。一不小心,就會搞出些併發的Bug,而且併發的Bug還很難除錯和測試(這也就是為啥不要輕易使用該玩意兒的原因)。
◇死鎖的問題
假如把前面介紹的操作步驟調換一下順序:生產者或消費者在操作完當前的緩衝區之後,先去獲取另一個緩衝區的鎖,再來釋放當前緩衝區的鎖。那會咋樣捏?
一旦兩個併發實體【同時】處理完各自緩衝區,然後【同時】去獲取對方擁有的鎖,那就會出現典型的死鎖(死鎖的詳細解釋參見“這裡”)場景。它倆從此陷入萬劫不復的境地。
★應用場景
介紹完併發問題,按照本系列的慣例,最後再來介紹一下雙緩衝區在某些場合的應用。
◇用於併發執行緒
線上程方式下,首先要考慮的是緩衝區的型別:到底用佇列方式還是環形方式。這方面的選擇依據在介紹環形緩衝區的時候已經闡述過了,此處不再囉嗦(省去不少口水)。
另一個需要注意的是,某些程式語言或者程式庫提供了的執行緒安全的緩衝區(比如 JDK 1.5 引入的 ArrayBlockingQueue)。由於這種緩衝區會自動為每次的讀寫進行同步/互斥,所以就把雙緩衝的優勢抵消掉了。因此,大夥兒在進行緩衝區選型的時候要避開這類緩衝區。
◇用於併發程式
在程式間使用雙緩衝,先得考察不同 IPC 型別的特點。由於今天討論雙緩衝的目的是降低同步/互斥的開銷,對於那些已經封裝了同步/互斥的 IPC 型別,就沒太大必要再去搞雙緩衝了(單憑這條就足以讓好多種 IPC 出局)。剩下的 IPC 型別中,比較適合用雙緩衝的主要是:共享記憶體和檔案。非常湊巧,這兩個玩意兒的特點和適用範圍在環形緩衝區的帖子裡面也已經介紹過了,俺又可以節省不少口水 :)
回到本系列的目錄
相關文章
- 架構設計:生產者/消費者模式[2]:佇列緩衝區架構模式佇列
- 架構設計:生產者/消費者模式[3]:環形緩衝區架構模式
- 架構設計:生產者/消費者模式[0]:概述架構模式
- 併發設計模式---生產者/消費者模式設計模式
- 生產者消費者模式模式
- 架構設計:生產者/消費者模式[1]:如何確定資料單元?架構模式
- 生產消費者模式模式
- Java中的設計模式(二):生產者-消費者模式與觀察者模式Java設計模式
- 九、生產者與消費者模式模式
- python 生產者消費者模式Python模式
- 阻塞佇列和生產者-消費者模式佇列模式
- Java 生產者消費者模式詳細分析Java模式
- 生產者消費者模型模型
- golang 併發程式設計之生產者消費者Golang程式設計
- java編寫生產者/消費者模式的程式。Java模式
- 使用BlockQueue實現生產者和消費者模式BloC模式
- Java多執行緒——生產者和消費者模式Java執行緒模式
- Java多執行緒之併發協作生產者消費者設計模式Java執行緒設計模式
- ActiveMQ 生產者和消費者demoMQ
- Java實現生產者和消費者Java
- 新手練習-消費者生產者模型模型
- Java實現生產者-消費者模型Java模型
- 生產者和消費者(.net實現)
- 使用wait()與notifyAll()實現生產者與消費者模式AI模式
- python執行緒通訊與生產者消費者模式Python執行緒模式
- Kafka的生產者優秀架構設計Kafka架構
- java實現生產者消費者問題Java
- linux 生產者與消費者問題Linux
- 多執行緒之生產者消費者執行緒
- 直觀理解生產者消費者問題
- java進階(40)--wait與notify(生產者與消費者模式)JavaAI模式
- 執行緒間的協作(2)——生產者與消費者模式執行緒模式
- 用Python多執行緒實現生產者消費者模式Python執行緒模式
- 生產者消費者模式,以及基於BlockingQueue的快速實現模式BloC
- 讀者寫者與生產者消費者應用場景
- Java多執行緒——生產者消費者示例Java執行緒
- 生產者與消費者之Android audioAndroid
- Qt基於QSemaphore的生產者消費者模型QT模型