架構設計:生產者/消費者模式 第3頁:隊列緩衝區

[2]:隊列緩衝區 shell

    通過前面兩個帖子的鋪墊,今天終於開始聊一些具體的編程技術了。因爲不一樣的緩衝區類型、不一樣的併發場景對於具體的技術實現有較大的影響。爲了深刻淺出、便 於大夥兒理解,我們先來介紹最傳統、最多見的方式。也就是單個生產者對應單個消費者,當中用隊列(FIFO)做緩衝。 編程

    關於併發的場景,在以前的帖子「進程還線程?是一個問題!」中,已經專門論述了進程和線程各自的優缺點,二者皆不可偏廢。因此,後面對各類緩衝區類型的介紹都會同時說起進程方式和線程方式。 安全

    ★線程方式 性能優化

    先來講一下併發線程中使用隊列的例子,以及相關的優缺點。 數據結構

    ◇內存分配的性能 併發

    在線程方式下,生產者和消費者各自是一個線程。生產者把數據寫入隊列頭(如下簡稱push),消費者從隊列尾部讀出數據(如下簡稱pop)。當隊列爲空,消費者就稍息(稍事休息);當隊列滿(達到最大長度),生產者就稍息。整個流程並不複雜。 編程語言

    那麼,上述過程會有什麼問題捏?一個主要的問題是關於內存分配的性能開銷。對於常見的隊列實現:在每次push時,可能涉及到堆內存的分配;在每次pop 時,可能涉及堆內存的釋放。假如生產者和消費者都很勤快,頻繁地push、pop,那內存分配的開銷就很可觀了。對於內存分配的開銷,用Java的同窗可 以參見前幾天的帖子「Java性能優化[1]」;對於用C/C++的同窗,想必對OS底層機制會更清楚,應該知道分配堆內存(new或malloc)會有 加鎖的開銷和用戶態/核心態切換的開銷。 性能

    那該怎麼辦捏?請聽下文分解,關於「生產者/消費者模式[3]:環形緩衝區」。 優化

    ◇同步和互斥的性能 操作系統

    另外,因爲兩個線程共用一個隊列,天然就會涉及到線程間諸如同步啊、互斥啊、死鎖啊等等勞心費神的事情。好在"操做系統"這門課程對此有詳細介紹,學過的 同窗應該還有點印象吧?對於沒學過這門課的同窗,也沒必要難過,網上相關的介紹挺多的(好比"這裏"),大夥本身去瞅一瞅。關於這方面的細節,咱今天就很少 囉嗦了。

    這會兒要細談的是,同步和互斥的性能開銷。在不少場合中,諸如信號量、互斥量等玩意兒的使用也是有不小的開銷的(某些狀況下,也可能致使用戶態/核心態切換)。若是像剛纔所說,生產者和消費者都很勤快,那這些開銷也不容小覷啊。

    這又該咋辦捏?請聽下文的下文分解,關於「生產者/消費者模式[4]:雙緩衝區」。

    ◇適用於隊列的場合

    剛纔盡批判了隊列的缺點,難道隊列方式就一無可取?非也。因爲隊列是很常見的數據結構,大部分編程語言都內置了隊列的支持(具體介紹見"這裏"),有些語 言甚至提供了線程安全的隊列(好比JDK 1.5引入的ArrayBlockingQueue)。所以,開發人員能夠撿現成,避免了從新發明輪子。

    因此,假如你的數據流量不是很大,採用隊列緩衝區的好處仍是很明顯的:邏輯清晰、代碼簡單、維護方便。比較符合KISS原則。

    ★進程方式

    說完了線程的方式,再來介紹基於進程的併發。

    跨進程的生產者/消費者模式,很是依賴於具體的進程間通信(IPC)方式。而IPC的種類名目繁多,不便於挨個列舉(畢竟口水有限)。所以我們挑選幾種跨平臺、且編程語言支持較多的IPC方式來講事兒。

    ◇匿名管道

    感受管道是最像隊列的IPC類型。生產者進程在管道的寫端放入數據;消費者進程在管道的讀端取出數據。整個的效果和線程中使用隊列很是相似,區別在於使用管道就無需操心線程安全、內存分配等雜事(操做系統暗中都幫你搞定了)。

    管道又分命名管道和匿名管道兩種,今天主要聊匿名管道。由於命名管道在不一樣的操做系統下差別較大(好比Win32和POSIX,在命名管道的API接口和 功能實現上都有較大差別;有些平臺不支持命名管道,好比Windows CE)。除了操做系統的問題,對於有些編程語言(好比Java)來講,命名管道是沒法使用的。因此我通常不推薦使用這玩意兒。

    其實匿名管道在不一樣平臺上的API接口,也是有差別的(好比Win32的CreatePipe和POSIX的pipe,用法就很不同)。可是咱們能夠僅 使用標準輸入和標準輸出(如下簡稱stdio)來進行數據的流入流出。而後利用shell的管道符把生產者進程和消費者進程關聯起來(沒據說過這種手法的 同窗,能夠看"這裏")。實際上,不少操做系統(尤爲是POSIX風格的)自帶的命令都充分利用了這個特性來實現數據的傳輸(好比more、grep 等)。

    這麼幹有幾個好處:

    一、基本上全部操做系統都支持在shell方式下使用管道符。所以很容易實現跨平臺。

    二、大部分編程語言都可以操做stdio,所以跨編程語言也就容易實現。

    三、剛纔已經提到,管道方式省卻了線程安全方面的雜事。有利於下降開發、調試成本。

    固然,這種方式也有自身的缺點:

    一、生產者進程和消費者進程必須得在同一臺主機上,沒法跨機器通信。這個缺點比較明顯。

    二、在一對一的狀況下,這種方式挺合用。但若是要擴展到一對多或者多對一,那就有點棘手了。因此這種方式的擴展性要打個折扣。假現在後要考慮相似的擴展,這個缺點就比較明顯。

    三、因爲管道是shell建立的,對於兩邊的進程不可見(程序看到的只是stdio)。在某些狀況下,致使程序不便於對管道進行操縱(好比調整管道緩衝區尺寸)。這個缺點不太明顯。

    四、最後,這種方式只能單向傳數據。好在大多數狀況下,消費者進程不須要傳數據給生產者進程。萬一你確實須要信息反饋(從消費者到生產者),那就費勁了。可能得考慮換種IPC方式。

相關文章
相關標籤/搜索