2009-03 作者: 編程隨想 來(lái)源: 編程隨想的博客
今天打算來(lái)介紹一下“生產(chǎn)者/消費(fèi)者模式 ” ,這玩意兒在很多開(kāi)發(fā)領(lǐng)域都能派上用場(chǎng)。由于該模式很重要,打算分幾個(gè)帖子來(lái)介紹。今天這個(gè)帖子先來(lái)掃盲一把。如果你對(duì)這個(gè)模式已經(jīng)比較了解,請(qǐng)?zhí)^(guò)本掃盲帖,直接看 下一個(gè)帖 子(關(guān)于該模式的具體應(yīng)用) 。
看到這里,可能有同學(xué)心中犯嘀咕了:在四人幫( GOF )的 23 種模式里面似乎沒(méi)聽(tīng)說(shuō)過(guò)這種嘛!其實(shí) GOF 那經(jīng)典的 23 種模式主要是基于 OO 的(從書名《 Design Patterns: Elements of Reusable Object-Oriented Software 》就可以看出來(lái))。而 Pattern 實(shí)際上即可以是 OO 的 Pattern ,也可以是非 OO 的 Pattern 的。
★簡(jiǎn)介
言歸正傳!在實(shí)際的軟件開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)碰到如下場(chǎng)景:某個(gè)模塊負(fù)責(zé)產(chǎn)生數(shù)據(jù),這些數(shù)據(jù)由另一個(gè)模塊來(lái)負(fù)責(zé)處理(此處的 模塊 是廣義的,可以是類、函數(shù)、線程、進(jìn)程等)。產(chǎn)生數(shù)據(jù)的模塊,就形象地稱為 生產(chǎn)者 ;而處理數(shù)據(jù)的模塊,就稱為 消費(fèi)者 。
單單抽象出生產(chǎn)者和消費(fèi)者,還夠不上是生產(chǎn)者/消費(fèi)者模式。該模式還需要有一個(gè)緩沖區(qū)處于生產(chǎn)者和消費(fèi)者之間,作為一個(gè)中介。生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū),而消費(fèi)者從緩沖區(qū)取出數(shù)據(jù)。大概的結(jié)構(gòu)如下圖。
為了不至于太抽象,我們舉一個(gè)寄信的例子(雖說(shuō)這年頭寄信已經(jīng)不時(shí)興,但這個(gè)例子還是比較貼切的)。假設(shè)你要寄一封平信,大致過(guò)程如下:
1 、你把信寫好 —— 相當(dāng)于生產(chǎn)者制造數(shù)據(jù)
2 、你把信放入郵筒 —— 相當(dāng)于生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū)
3 、郵遞員把信從郵筒取出 —— 相當(dāng)于消費(fèi)者把數(shù)據(jù)取出緩沖區(qū)
4 、郵遞員把信拿去郵局做相應(yīng)的處理 —— 相當(dāng)于消費(fèi)者處理數(shù)據(jù)
★優(yōu)點(diǎn)
可能有同學(xué)會(huì)問(wèn)了:這個(gè)緩沖區(qū)有什么用捏?為什么不讓生產(chǎn)者直接調(diào)用消費(fèi)者的某個(gè)函數(shù),直接把數(shù)據(jù)傳遞過(guò)去?搞出這么一個(gè)緩沖區(qū)作甚?
其實(shí)這里面是大有講究的,大概有如下一些好處。
◇解耦
假設(shè)生產(chǎn)者和消費(fèi)者分別是兩個(gè)類。如果讓生產(chǎn)者直接調(diào)用消費(fèi)者的某個(gè)方法,那么生產(chǎn)者對(duì)于消費(fèi)者就會(huì)產(chǎn)生依賴(也就是耦合)。將來(lái)如果消費(fèi)者的代碼發(fā)生變化,可能會(huì)影響到生產(chǎn)者。 而如果兩者都依賴于某個(gè)緩沖區(qū),兩者之間不直接依賴,耦合也就相應(yīng)降低了。
接著上述的例子,如果不使用郵筒(也就是緩沖區(qū)),你必須得把信直接交給郵遞員。有同學(xué)會(huì)說(shuō),直接給郵遞員不是挺簡(jiǎn)單的嘛?其實(shí)不簡(jiǎn)單,你必須得認(rèn)識(shí)誰(shuí)是郵遞員,才能把信給他(光憑身上穿的制服,萬(wàn)一有人假冒,就慘了)。這就產(chǎn)生和你和郵遞員之間的依賴(相當(dāng)于生產(chǎn)者和消費(fèi)者的強(qiáng)耦合)。萬(wàn)一哪天郵遞員換人了,你還要重新認(rèn)識(shí)一下(相當(dāng)于消費(fèi)者變化導(dǎo)致修改生產(chǎn)者代碼)。而郵筒相對(duì)來(lái)說(shuō)比較固定,你依賴它的成本就比較低(相當(dāng)于和緩沖區(qū)之間的弱耦合)。
◇支持并發(fā)( concurrency )
生產(chǎn)者直接調(diào)用消費(fèi)者的某個(gè)方法,還有另一個(gè)弊端。由于函數(shù)調(diào)用是同步的(或者叫阻塞的),在消費(fèi)者的方法沒(méi)有返回之前,生產(chǎn)者只好一直等在那邊。萬(wàn)一消費(fèi)者處理數(shù)據(jù)很慢,生產(chǎn)者就會(huì)白白糟蹋大好時(shí)光。
使用了生產(chǎn)者/消費(fèi)者模式之后,生產(chǎn)者和消費(fèi)者可以是兩個(gè)獨(dú)立的并發(fā)主體(常見(jiàn)并發(fā)類型有進(jìn)程和線程兩種,后面的帖子會(huì)講兩種并發(fā)類型下的應(yīng)用)。生產(chǎn)者把制造出來(lái)的數(shù)據(jù)往緩沖區(qū)一丟,就可以再去生產(chǎn)下一個(gè)數(shù)據(jù)。 基本上不用依賴消費(fèi)者的處理速度 。
其實(shí)當(dāng)初這個(gè)模式,主要就是用來(lái)處理并發(fā)問(wèn)題的。
從寄信的例子來(lái)看。如果沒(méi)有郵筒,你得拿著信傻站在路口等郵遞員過(guò)來(lái)收(相當(dāng)于生產(chǎn)者阻塞);又或者郵遞員得挨家挨戶問(wèn),誰(shuí)要寄信(相當(dāng)于消費(fèi)者輪詢)。不管是哪種方法,都挺土的。
◇支持忙閑不均
緩沖區(qū)還有另一個(gè)好處。如果制造數(shù)據(jù)的速度時(shí)快時(shí)慢,緩沖區(qū)的好處就體現(xiàn)出來(lái)了。當(dāng)數(shù)據(jù)制造快的時(shí)候,消費(fèi)者來(lái)不及處理,未處理的數(shù)據(jù)可以 暫存 在緩沖區(qū)中。等生產(chǎn)者的制造速度慢下來(lái),消費(fèi)者再慢慢處理掉。
為了充分復(fù)用,我們?cè)倌眉男诺睦觼?lái)說(shuō)事。假設(shè)郵遞員一次只能帶走 1000 封信。萬(wàn)一某次碰上情人節(jié)(也可能是圣誕節(jié))送賀卡,需要寄出去的信超過(guò) 1000 封,這時(shí)候郵筒這個(gè)緩沖區(qū)就派上用場(chǎng)了。郵遞員把來(lái)不及帶走的信暫存在郵筒中,等下次過(guò)來(lái)時(shí)再拿走。
費(fèi)了這么多口水,希望原先不太了解生產(chǎn)者/消費(fèi)者模式的同學(xué)能夠明白它是怎么一回事。然后在下一個(gè)帖子中,我們來(lái)說(shuō)說(shuō) 如何確定數(shù)據(jù)單元 。
另外,為了方便閱讀,把本系列帖子的目錄整理如下:
2 、 隊(duì)列緩沖區(qū)
3 、 環(huán)形 緩沖區(qū)
4 、 雙緩沖區(qū)
5 、 ......
既然 前一個(gè)帖子 已經(jīng)搞過(guò)掃盲了,那接下來(lái)應(yīng)該開(kāi)始聊一些具體的編程技術(shù)問(wèn)題了。不過(guò)在進(jìn)入具體的技術(shù)細(xì)節(jié)之前,咱們先要搞明白一個(gè)問(wèn)題:如何確定數(shù)據(jù)單元?只有把數(shù)據(jù)單元分析清楚,后面的技術(shù)設(shè)計(jì)才好搞。
★啥是數(shù)據(jù)單元
何謂數(shù)據(jù)單元捏?簡(jiǎn)單地說(shuō),每次生產(chǎn)者放到緩沖區(qū)的,就是一個(gè)數(shù)據(jù)單元;每次消費(fèi)者從緩沖區(qū)取出的,也是一個(gè)數(shù)據(jù)單元。對(duì)于 前一個(gè)帖子 中寄信的例子,我們可以把每一封單獨(dú)的信件看成是一個(gè)數(shù)據(jù)單元。
不過(guò)光這么介紹,太過(guò)于簡(jiǎn)單,無(wú)助于大伙兒分析出這玩意兒。所以,后面咱們來(lái)看一下數(shù)據(jù)單元需要具備哪些特性。搞明白這些特性之后,就容易從復(fù)雜的業(yè)務(wù)邏輯中分析出適合做數(shù)據(jù)單元的東西了。
★數(shù)據(jù)單元的特性
分析數(shù)據(jù)單元,需要考慮如下幾個(gè)方面的特性:
◇關(guān)聯(lián)到業(yè)務(wù)對(duì)象
首先,數(shù)據(jù)單元必須關(guān)聯(lián)到某種業(yè)務(wù)對(duì)象。在考慮該問(wèn)題的時(shí)候,你必須深刻理解當(dāng)前這個(gè)生產(chǎn)者/消費(fèi)者模式所對(duì)應(yīng)的 業(yè)務(wù)邏輯 ,才能夠作出合適的判斷。
由于“寄信 ” 這個(gè)業(yè)務(wù)邏輯比較簡(jiǎn)單,所以大伙兒很容易就可以判斷出數(shù)據(jù)單元是啥。但現(xiàn)實(shí)生活中,往往沒(méi)這么樂(lè)觀。大多數(shù)業(yè)務(wù)邏輯都比較復(fù)雜,當(dāng)中包含的業(yè)務(wù)對(duì)象是層次繁多、類型各異。在這種情況下,就不易作出決策了。
這一步很重要,如果選錯(cuò)了業(yè)務(wù)對(duì)象,會(huì)導(dǎo)致后續(xù)程序設(shè)計(jì)和編碼實(shí)現(xiàn)的復(fù)雜度大為上升,增加了開(kāi)發(fā)和維護(hù)成本。
(例如,在進(jìn)行 MPEG-4 碼流緩沖時(shí),可考慮以一幀作為數(shù)據(jù)單元,也可以以一個(gè) GOP 作為數(shù)據(jù)單元,視具體業(yè)務(wù)而定)
◇完整性
所謂完整性,就是在傳輸過(guò)程中,要保證該數(shù)據(jù)單元的完整。要么 整個(gè) 數(shù)據(jù)單元被傳遞到消費(fèi)者,要么完全沒(méi)有傳遞到消費(fèi)者。不允許出現(xiàn)部分傳遞的情形。
對(duì)于寄信來(lái)說(shuō),你 不能 把半封信放入郵筒;同樣的,郵遞員從郵筒中拿信,也 不能 只拿出信的一部分。
(例如,在進(jìn)行 MPEG-4 碼流緩沖時(shí),數(shù)據(jù)必須一整幀或一個(gè)完整的 GOP )
◇獨(dú)立性
所謂獨(dú)立性,就是各個(gè)數(shù)據(jù)單元之間沒(méi)有互相依賴,某個(gè)數(shù)據(jù)單元傳輸失敗不應(yīng)該影響已經(jīng)完成傳輸?shù)膯卧灰膊粦?yīng)該影響尚未傳輸?shù)膯卧?
為啥會(huì)出現(xiàn)傳輸失敗捏?假如生產(chǎn)者的生產(chǎn)速度在一段時(shí)間內(nèi)一直超過(guò)消費(fèi)者的處理速度,那就會(huì)導(dǎo)致緩沖區(qū)不斷增長(zhǎng)并達(dá)到上限,之后的數(shù)據(jù)單元就會(huì)被丟棄。如果數(shù)據(jù)單元相互獨(dú)立,等到生產(chǎn)者的速度降下來(lái)之后,后續(xù)的數(shù)據(jù)單元繼續(xù)處理,不會(huì)受到牽連;反之,如果數(shù)據(jù)單元之間有某種耦合,導(dǎo)致被丟棄的數(shù)據(jù)單元會(huì)影響到后續(xù)其它單元的處理,那就會(huì)使程序邏輯變得非常復(fù)雜。
對(duì)于寄信來(lái)說(shuō),某封信弄丟了,不會(huì)影響后續(xù)信件的送達(dá);當(dāng)然更不會(huì)影響已經(jīng)送達(dá)的信件。
(例如,在進(jìn)行 MPEG-4 碼流緩沖時(shí),幀與幀之間一般有差向關(guān)聯(lián),而一個(gè) GOP 是獨(dú)立的)
◇顆粒度
前面提到,數(shù)據(jù)單元需要關(guān)聯(lián)到某種業(yè)務(wù)對(duì)象。那么數(shù)據(jù)單元和業(yè)務(wù)對(duì)象是否要一一對(duì)應(yīng)捏?很多場(chǎng)合確實(shí)是一一對(duì)應(yīng)的。
不過(guò),有時(shí)出于性能等因素的考慮,也可能會(huì) 把 N 個(gè)業(yè)務(wù)對(duì)象打包成一個(gè)數(shù)據(jù)單元 (比如 MPEG-4 碼流中多幀組成 GOP )。那么,這個(gè) N 該如何取值就是顆粒度的考慮了。顆粒度的大小是有講究的。太大的顆粒度可能會(huì)造成某種浪費(fèi);太小的顆粒度可能會(huì)造成性能問(wèn)題。顆粒度的權(quán)衡要基于多方面的因素,以及一些經(jīng)驗(yàn)值的考量。
還是拿寄信的例子。如果顆粒度過(guò)小(比如設(shè)定為 1 ),那郵遞員每次只取出 1 封信。如果信件多了,那就得來(lái)回跑好多趟,浪費(fèi)了時(shí)間。
如果顆粒度太大(比如設(shè)定為 100 ),那寄信的人得等到湊滿 100 封信才拿去放入郵筒。假如平時(shí)很少寫信,就得等上很久,也不太爽。
可能有同學(xué)會(huì)問(wèn):生產(chǎn)者和消費(fèi)者的顆粒度能否設(shè)置成不同大小(比如對(duì)于寄信人設(shè)置成 1 ,對(duì)于郵遞員設(shè)置成 100 )。當(dāng)然,理論上可以這么干,但是在某些情況下會(huì)增加程序邏輯和代碼實(shí)現(xiàn)的復(fù)雜度。后面討論具體技術(shù)細(xì)節(jié)時(shí),或許會(huì)聊到這個(gè)問(wèn)題。
(例如,在進(jìn)行 MPEG-4 碼流緩沖時(shí),視情況一次可緩沖多個(gè) GOP )
好,數(shù)據(jù)單元的話題就說(shuō)到這。希望通過(guò)本帖子,大伙兒能夠搞明白數(shù)據(jù)單元到底是怎么一回事。下一個(gè)帖子,咱們來(lái)聊一下“ 基于隊(duì)列的緩沖區(qū) ” ,技術(shù)上如何實(shí)現(xiàn)。
經(jīng)過(guò)前面兩個(gè)帖子的鋪墊,今天終于開(kāi)始聊一些具體的編程技術(shù)了。由于不同的緩沖區(qū)類型、不同的并發(fā)場(chǎng)景對(duì)于具體的技術(shù)實(shí)現(xiàn)有較大的影響。為了深入淺出、便于大伙兒理解,咱們先來(lái)介紹最傳統(tǒng)、最常見(jiàn)的方式。也就是單個(gè)生產(chǎn)者對(duì)應(yīng)單個(gè)消費(fèi)者,當(dāng)中用隊(duì)列( FIFO )作緩沖。
關(guān)于并發(fā)的場(chǎng)景,在之前的帖子“ 進(jìn)程還線程?是一個(gè)問(wèn)題! ” 中,已經(jīng)專門論述了進(jìn)程和線程各自的優(yōu)缺點(diǎn),兩者皆不可偏廢。所以,后面對(duì)各種緩沖區(qū)類型的介紹都會(huì)同時(shí)提及進(jìn)程方式和線程方式。
★線程方式
先來(lái)說(shuō)一下并發(fā)線程中使用隊(duì)列的例子,以及相關(guān)的優(yōu)缺點(diǎn)。
◇內(nèi)存分配的性能
在線程方式下,生產(chǎn)者和消費(fèi)者各自是一個(gè)線程。生產(chǎn)者把數(shù)據(jù)寫入隊(duì)列頭(以下簡(jiǎn)稱 push ),消費(fèi)者從隊(duì)列尾部讀出數(shù)據(jù)(以下簡(jiǎn)稱 pop )。當(dāng)隊(duì)列為空,消費(fèi)者就稍息(稍事休息);當(dāng)隊(duì)列滿(達(dá)到最大長(zhǎng)度),生產(chǎn)者就稍息。整個(gè)流程并不復(fù)雜。
那么,上述過(guò)程會(huì)有什么問(wèn)題捏?一個(gè)主要的問(wèn)題是關(guān)于內(nèi)存分配的性能開(kāi)銷。對(duì)于常見(jiàn)的隊(duì)列實(shí)現(xiàn):在每次 push 時(shí),可能涉及到堆內(nèi)存的分配;在每次 pop 時(shí),可能涉及堆內(nèi)存的釋放。假如生產(chǎn)者和消費(fèi)者都很勤快,頻繁地 push 、 pop ,那內(nèi)存分配的開(kāi)銷就很可觀了。對(duì)于內(nèi)存分配的開(kāi)銷,用 Java 的同學(xué)可以參見(jiàn)前幾天的帖子“ Java 性能優(yōu)化[1] ” ;對(duì)于用 C/C++ 的同學(xué),想必對(duì) OS 底層機(jī)制會(huì)更清楚,應(yīng)該知道分配堆內(nèi)存( new 或 malloc )會(huì)有加鎖的開(kāi)銷和 用戶態(tài)/核心態(tài)切換 的開(kāi)銷。
那該怎么辦捏?請(qǐng)聽(tīng)下文分解,關(guān)于“ 生產(chǎn)者/消費(fèi)者模式[3] :環(huán)形緩沖區(qū) ” 。
◇同步和互斥的性能
另外,由于兩個(gè)線程共用一個(gè)隊(duì)列,自然就會(huì)涉及到線程間諸如同步啊、互斥啊、死鎖啊等等勞心費(fèi)神的事情。好在“操作系統(tǒng) ” 這門課程對(duì)此有詳細(xì)介紹,學(xué)過(guò)的同學(xué)應(yīng)該還有點(diǎn)印象吧?對(duì)于沒(méi)學(xué)過(guò)這門課的同學(xué),也不必難過(guò),網(wǎng)上相關(guān)的介紹挺多的(比如“ 這里 ” ),大伙自己去瞅一瞅。關(guān)于這方面的細(xì)節(jié),咱今天就不多啰嗦了。
這會(huì)兒要細(xì)談的是,同步和互斥的性能開(kāi)銷。在很多場(chǎng)合中,諸如信號(hào)量、互斥量等玩意兒的使用也是有不小的開(kāi)銷的(某些情況下,也可能導(dǎo)致用戶態(tài)/核心態(tài)切換)。如果像剛才所說(shuō),生產(chǎn)者和消費(fèi)者都很勤快,那這些開(kāi)銷也不容小覷啊。
這又該咋辦捏?請(qǐng)聽(tīng)下文的分解,關(guān)于“ 生產(chǎn)者/消費(fèi)者模式[4] :雙緩沖區(qū) ” 。
◇適用于隊(duì)列的場(chǎng)合
剛才盡批判了隊(duì)列的缺點(diǎn),難道隊(duì)列方式就一無(wú)是處?非也。由于隊(duì)列是很常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),大部分編程語(yǔ)言都內(nèi)置了隊(duì)列的支持(具體介紹見(jiàn)“ 這里 ” ),有些語(yǔ)言甚至提供了線程安全的隊(duì)列(比如 JDK 1.5 引入的 ArrayBlockingQueue )。因此,開(kāi)發(fā)人員可以撿現(xiàn)成,避免了重新發(fā)明輪子。
所以, 假如你的數(shù)據(jù)流量不是很大,采用隊(duì)列緩沖區(qū)的好處還是很明顯的 :邏輯清晰、代碼簡(jiǎn)單、維護(hù)方便。比較符合 KISS 原則。
★進(jìn)程方式
說(shuō)完了線程的方式,再來(lái)介紹基于進(jìn)程的并發(fā)。
跨進(jìn)程的生產(chǎn)者/消費(fèi)者模式,非常依賴于具體的進(jìn)程間通訊( IPC )方式。而 IPC 的種類名目繁多,不便于挨個(gè)列舉(畢竟口水有限)。因此咱們挑選幾種跨平臺(tái)、且編程語(yǔ)言支持較多的 IPC 方式來(lái)說(shuō)事兒。
◇匿名管道
感覺(jué)管道是最像隊(duì)列的 IPC 類型。生產(chǎn)者進(jìn)程在管道的 寫端 放入數(shù)據(jù);消費(fèi)者進(jìn)程在管道的 讀端 取出數(shù)據(jù)。整個(gè)的效果和線程中使用隊(duì)列非常類似,區(qū)別在于使用管道就無(wú)需操心線程安全、內(nèi)存分配等瑣事(操作系統(tǒng)暗中都幫你搞定了)。
管道又分 命名管道 和 匿名管道 兩種,今天主要聊匿名管道。因?yàn)槊艿涝诓煌牟僮飨到y(tǒng)下差異較大(比如 Win32 和 POSIX ,在命名管道的 API 接口和功能實(shí)現(xiàn)上都有較大差異;有些平臺(tái)不支持命名管道,比如 Windows CE ) 。 除了操作系統(tǒng)的問(wèn)題,對(duì)于有些編程語(yǔ)言(比如 Java )來(lái)說(shuō),命名管道是無(wú)法使用的。所以我一般不推薦使用這玩意兒。
其實(shí)匿名管道在不同平臺(tái)上的 API 接口,也是有差異的(比如 Win32 的 CreatePipe 和 POSIX 的 pipe ,用法就很不一樣)。但是我們可以僅使用標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出(以下簡(jiǎn)稱 stdio )來(lái)進(jìn)行數(shù)據(jù)的流入流出。然后利用 shell 的管道符把生產(chǎn)者進(jìn)程和消費(fèi)者進(jìn)程關(guān)聯(lián)起來(lái)(沒(méi)聽(tīng)說(shuō)過(guò)這種手法的同學(xué),可以看“ 這里 ” )。實(shí)際上,很多操作系統(tǒng)(尤其是 POSIX 風(fēng)格的)自帶的命令都充分利用了這個(gè)特性來(lái)實(shí)現(xiàn)數(shù)據(jù)的傳輸(比如 more 、 grep 等)。
這么干有幾個(gè)好處:
1 、基本上所有操作系統(tǒng)都支持在 shell 方式下使用管道符。因此很容易實(shí)現(xiàn)跨平臺(tái)。
2 、大部分編程語(yǔ)言都能夠操作 stdio ,因此跨編程語(yǔ)言也就容易實(shí)現(xiàn)。
3 、剛才已經(jīng)提到,管道方式省卻了線程安全方面的瑣事。有利于降低開(kāi)發(fā)、調(diào)試成本。
當(dāng)然,這種方式也有自身的缺點(diǎn):
1 、生產(chǎn)者進(jìn)程和消費(fèi)者進(jìn)程必須得在同一臺(tái)主機(jī)上,無(wú)法跨機(jī)器通訊。這個(gè)缺點(diǎn)比較明顯。
2 、在一對(duì)一的情況下,這種方式挺合用。但如果要擴(kuò)展到一對(duì)多或者多對(duì)一,那就有點(diǎn)棘手了。所以這種方式的擴(kuò)展性要打個(gè)折扣。假如今后要考慮類似的擴(kuò)展,這個(gè)缺點(diǎn)就比較明顯。
3 、由于管道是 shell 創(chuàng)建的,對(duì)于兩邊的進(jìn)程不可見(jiàn)(程序看到的只是 stdio )。在某些情況下,導(dǎo)致程序不便于對(duì)管道進(jìn)行操縱(比如調(diào)整管道緩沖區(qū)尺寸)。這個(gè)缺點(diǎn)不太明顯。
4 、最后,這種方式只能單向傳數(shù)據(jù)。好在大多數(shù)情況下,消費(fèi)者進(jìn)程不需要傳數(shù)據(jù)給生產(chǎn)者進(jìn)程。萬(wàn)一你確實(shí)需要信息反饋(從消費(fèi)者到生產(chǎn)者),那就費(fèi)勁了。可能得考慮換種 IPC 方式。
順便補(bǔ)充幾個(gè)注意事項(xiàng),大伙兒留意一下:
1 、對(duì) stdio 進(jìn)行讀寫操作是以阻塞方式進(jìn)行。比如管道中沒(méi)有數(shù)據(jù),消費(fèi)者進(jìn)程的讀操作就會(huì)一直停在哪兒,直到管道中重新有數(shù)據(jù)。
2 、由于 stdio 內(nèi)部帶有自己的緩沖區(qū)(這緩沖區(qū)和管道緩沖區(qū)是兩碼事),有時(shí)會(huì)導(dǎo)致一些不太爽的現(xiàn)象(比如生產(chǎn)者進(jìn)程輸出了數(shù)據(jù),但消費(fèi)者進(jìn)程沒(méi)有立即讀到)。具體的細(xì)節(jié),大伙兒可以看“ 這里 ” 。
◇ SOCKET ( TCP 方式)
基于 TCP 方式的 SOCKET 通訊是又一個(gè)類似于隊(duì)列的 IPC 方式。它同樣保證了數(shù)據(jù)的順序到達(dá);同樣有緩沖的機(jī)制。而且這玩意兒也是跨平臺(tái)和跨語(yǔ)言的,和剛才介紹的 shell 管道符方式類似。
SOCKET 相比 shell 管道符的方式,有啥優(yōu)點(diǎn)捏?主要有如下幾個(gè)優(yōu)點(diǎn):
1 、 SOCKET 方式可以跨機(jī)器(便于實(shí)現(xiàn)分布式)。這是主要優(yōu)點(diǎn)。
2 、 SOCKET 方式便于將來(lái)擴(kuò)展成為多對(duì)一或者一對(duì)多。這也是主要優(yōu)點(diǎn)。
3 、 SOCKET 可以設(shè)置阻塞和非阻塞方法,用起來(lái)比較靈活。這是次要優(yōu)點(diǎn)。
4 、 SOCKET 支持雙向通訊,有利于消費(fèi)者反饋信息。
當(dāng)然有利就有弊。相對(duì)于上述 shell 管道的方式,使用 SOCKET 在編程上會(huì)更復(fù)雜一些。好在前人已經(jīng)做了大量的工作,搞出很多 SOCKET 通訊庫(kù)和框架給大伙兒用(比如 C++ 的 ACE 庫(kù)、 Python 的 Twisted )。借助于這些第三方的庫(kù)和框架, SOCKET 方式用起來(lái)還是比較爽的。由于具體的網(wǎng)絡(luò)通訊庫(kù)該怎么用不是本系列的重點(diǎn),此處就不細(xì)說(shuō)了。
雖然 TCP 在很多方面比 UDP 可靠,但鑒于跨機(jī)器通訊先天的不可預(yù)料性(比如網(wǎng)線可能被某傻 X 給拔錯(cuò)了,網(wǎng)絡(luò)的忙閑波動(dòng)可能很大),在程序設(shè)計(jì)上我們還是要多留一手。具體該如何做捏?可以在生產(chǎn)者 進(jìn)程 和消費(fèi)者 進(jìn)程 內(nèi)部各自再引入基于 線程 的“生產(chǎn)者/消費(fèi)者模式 ” 。這話聽(tīng)著像繞口令,為了便于理解,畫張圖給大伙兒瞅一瞅。
這么做的關(guān)鍵點(diǎn)在于把代碼分為兩部分:生產(chǎn)線程和消費(fèi)線程屬于和業(yè)務(wù)邏輯相關(guān)的代碼(和通訊邏輯無(wú)關(guān));發(fā)送線程和接收線程屬于通訊相關(guān)的代碼(和業(yè)務(wù)邏輯無(wú)關(guān))。
這樣的好處是很明顯的,具體如下:
1 、能夠應(yīng)對(duì) 暫時(shí)性 的網(wǎng)絡(luò)故障。并且在網(wǎng)絡(luò)故障解除后,能夠繼續(xù)工作。
2 、網(wǎng)絡(luò)故障的應(yīng)對(duì)處理方式(比如斷開(kāi)后的嘗試重連),只影響發(fā)送和接收線程,不會(huì)影響生產(chǎn)線程和消費(fèi)線程(業(yè)務(wù)邏輯部分)。
3 、具體的 SOCKET 方式(阻塞和非阻塞)只影響發(fā)送和接收線程,不影響生產(chǎn)線程和消費(fèi)線程(業(yè)務(wù)邏輯部分)。
4 、不依賴 TCP 自身的發(fā)送緩沖區(qū)和接收緩沖區(qū)。(默認(rèn)的 TCP 緩沖區(qū)的大小可能無(wú)法滿足實(shí)際要求)
5 、業(yè)務(wù)邏輯的變化(比如業(yè)務(wù)需求變更)不影響發(fā)送線程和接收線程。
針對(duì)上述的最后一條,再多啰嗦幾句。如果整個(gè)業(yè)務(wù)系統(tǒng)中有多個(gè)進(jìn)程是采用上述的模式,那或許可以重構(gòu)一把:在業(yè)務(wù)邏輯代碼和通訊邏輯代碼之間切一刀,把業(yè)務(wù)邏輯無(wú)關(guān)的部分封裝成一個(gè)通訊中間件(說(shuō)中間件顯得比較牛 X :- )。如果大伙兒對(duì)這玩意兒有興趣,以后專門開(kāi)個(gè)帖子聊。
下一個(gè)帖子 ,咱們介紹一下環(huán)形緩沖區(qū)的話題。
前一個(gè)帖子 提及了隊(duì)列緩沖區(qū)可能存在的性能問(wèn)題及解決方法:環(huán)形緩沖區(qū)。今天就專門來(lái)描述一下這個(gè)話題。
為了防止有人給咱扣上“過(guò)度設(shè)計(jì) ” 的大帽子,事先聲明一下:只有當(dāng)存儲(chǔ)空間的分配/釋放非常 頻繁 并且確實(shí)產(chǎn)生了 明顯 的影響,你才應(yīng)該考慮環(huán)形緩沖區(qū)的使用。否則的話,還是老老實(shí)實(shí)用最基本、最簡(jiǎn)單的 隊(duì)列緩沖區(qū) 吧。還有一點(diǎn)需要說(shuō)明一下:本文所提及的“ 存儲(chǔ)空間 ” ,不僅包括內(nèi)存,還可能包括諸如硬盤之類的存儲(chǔ)介質(zhì)。
★環(huán)形緩沖區(qū) vs 隊(duì)列緩沖區(qū)
◇外部接口相似
在介紹環(huán)形緩沖區(qū)之前,咱們先來(lái)回顧一下普通的隊(duì)列。普通的隊(duì)列有一個(gè)寫入端和一個(gè)讀出端。隊(duì)列為空的時(shí)候,讀出端無(wú)法讀取數(shù)據(jù);當(dāng)隊(duì)列滿(達(dá)到最大尺寸)時(shí),寫入端無(wú)法寫入數(shù)據(jù)。
對(duì)于使用者來(lái)講,環(huán)形緩沖區(qū)和隊(duì)列緩沖區(qū)是一樣的。它也有一個(gè)寫入端(用于 push )和一個(gè)讀出端(用于 pop ),也有緩沖區(qū)“滿 ” 和“空 ” 的狀態(tài)。所以,從隊(duì)列緩沖區(qū)切換到環(huán)形緩沖區(qū),對(duì)于使用者來(lái)說(shuō)能比較平滑地過(guò)渡。
◇內(nèi)部結(jié)構(gòu)迥異
雖然兩者的對(duì)外接口差不多,但是內(nèi)部結(jié)構(gòu)和運(yùn)作機(jī)制有很大差別。隊(duì)列的內(nèi)部結(jié)構(gòu)此處就不多啰嗦了。重點(diǎn)介紹一下環(huán)形緩沖區(qū)的內(nèi)部結(jié)構(gòu)。
大伙兒可以把環(huán)形緩沖區(qū)的讀出端(以下簡(jiǎn)稱 R )和寫入端(以下簡(jiǎn)稱 W )想象成是兩個(gè)人在體育場(chǎng)跑道上追逐( R 追 W )。當(dāng) R 追上 W 的時(shí)候,就是緩沖區(qū)為空;當(dāng) W 追上 R 的時(shí)候( W 比 R 多跑一圈),就是緩沖區(qū)滿。
為了形象起見(jiàn),去找來(lái)一張圖并略作修改,如下:
從上圖可以看出,環(huán)形緩沖區(qū)所有的 push 和 pop 操作都是在一個(gè) 固定 的存儲(chǔ)空間內(nèi)進(jìn)行。而隊(duì)列緩沖區(qū)在 push 的時(shí)候,可能會(huì)分配存儲(chǔ)空間用于存儲(chǔ)新元素;在 pop 時(shí),可能會(huì)釋放廢棄元素的存儲(chǔ)空間。所以環(huán)形方式相比隊(duì)列方式,少掉了對(duì)于緩沖區(qū)元素所用存儲(chǔ)空間的分配、釋放。這是環(huán)形緩沖區(qū)的一個(gè)主要優(yōu)勢(shì)。
★環(huán)形緩沖區(qū)的實(shí)現(xiàn)
如果你手頭已經(jīng)有現(xiàn)成的環(huán)形緩沖區(qū)可供使用,并且你對(duì)環(huán)形緩沖區(qū)的內(nèi)部實(shí)現(xiàn)不感興趣,可以跳過(guò)這段。
◇數(shù)組方式 vs 鏈表方式
環(huán)形緩沖區(qū)的內(nèi)部實(shí)現(xiàn),即可基于數(shù)組(此處的數(shù)組,泛指連續(xù)存儲(chǔ)空間)實(shí)現(xiàn),也可基于鏈表實(shí)現(xiàn)。
數(shù)組在物理存儲(chǔ)上是一維的連續(xù)線性結(jié)構(gòu),可以在初始化時(shí),把存儲(chǔ)空間 一次性 分配好,這是數(shù)組方式的優(yōu)點(diǎn)。但是要使用數(shù)組來(lái)模擬環(huán),你必須在邏輯上把數(shù)組的頭和尾相連。在順序遍歷數(shù)組時(shí),對(duì)尾部元素(最后一個(gè)元素)要作一下特殊處理。訪問(wèn)尾部元素的下一個(gè)元素時(shí),要重新回到頭部元素(第 0 個(gè)元素)。如下圖所示:
使用鏈表的方式,正好和數(shù)組相反:鏈表省去了頭尾相連的特殊處理。但是鏈表在初始化的時(shí)候比較繁瑣,而且在有些場(chǎng)合(比如后面提到的跨進(jìn)程的 IPC )不太方便使用。
◇讀寫操作
環(huán)形緩沖區(qū)要維護(hù)兩個(gè)索引,分別對(duì)應(yīng)寫入端( W )和讀取端( R )。寫入( push )的時(shí)候,先確保環(huán)沒(méi)滿,然后把數(shù)據(jù)復(fù)制到 W 所對(duì)應(yīng)的元素,最后 W 指向下一個(gè)元素;讀取( pop )的時(shí)候,先確保環(huán)沒(méi)空,然后返回 R 對(duì)應(yīng)的元素,最后 R 指向下一個(gè)元素。
◇判斷 ” 空 ” 和 ” 滿 ”
上述的操作并不復(fù)雜,不過(guò)有一個(gè)小小的麻煩:空環(huán)和滿環(huán)的時(shí)候, R 和 W 都指向同一個(gè)位置!這樣就無(wú)法判斷到底是 ” 空 ” 還是 ” 滿 ” 。大體上有兩種方法可以解決該問(wèn)題。
辦法 1 :始終保持一個(gè)元素不用
當(dāng)空環(huán)的時(shí)候, R 和 W 重疊。當(dāng) W 比 R 跑得快,追到距離 R 還有一個(gè)元素間隔的時(shí)候,就認(rèn)為環(huán)已經(jīng)滿。當(dāng)環(huán)內(nèi)元素占用的存儲(chǔ)空間較大的時(shí)候,這種辦法顯得很土(浪費(fèi)空間)。
辦法 2 :維護(hù)額外變量
如果不喜歡上述辦法,還可以采用額外的變量來(lái)解決。比如可以用一個(gè)整數(shù)記錄當(dāng)前環(huán)中已經(jīng)保存的元素個(gè)數(shù)(該整數(shù) >=0 )。當(dāng) R 和 W 重疊的時(shí)候,通過(guò)該變量就可以知道是 ” 空 ” 還是 ” 滿 ” 。
◇元素的存儲(chǔ)
由于環(huán)形緩沖區(qū)本身就是要降低存儲(chǔ)空間分配的開(kāi)銷,因此緩沖區(qū)中元素的類型要選好。盡量存儲(chǔ) 值類型 的數(shù)據(jù),而不要存儲(chǔ) 指針(引用)類型 的數(shù)據(jù)。因?yàn)橹羔橆愋偷臄?shù)據(jù)又會(huì)引起存儲(chǔ)空間(比如堆內(nèi)存)的分配和釋放,使得環(huán)形緩沖區(qū)的效果打折扣。
★應(yīng)用場(chǎng)合
剛才介紹了環(huán)形緩沖區(qū)內(nèi)部的實(shí)現(xiàn)機(jī)制。按照 前一個(gè)帖子 的慣例,我們來(lái)介紹一下在線程和進(jìn)程方式下的使用。
如果你所使用的編程語(yǔ)言和開(kāi)發(fā)庫(kù)中帶有現(xiàn)成的、 成熟的 環(huán)形緩沖區(qū),強(qiáng)烈建議使用現(xiàn)成的庫(kù),不要重新制造輪子;確實(shí)找不到現(xiàn)成的,才考慮自己實(shí)現(xiàn)。如果你純粹是業(yè)余時(shí)間練練手,那另當(dāng)別論。
◇用于并發(fā)線程
和線程中的隊(duì)列緩沖區(qū)類似,線程中的環(huán)形緩沖區(qū)也要考慮線程安全的問(wèn)題。除非你使用的環(huán)形緩沖區(qū)的庫(kù)已經(jīng)幫你實(shí)現(xiàn)了線程安全,否則你還是得自己動(dòng)手搞定。線程方式下的環(huán)形緩沖區(qū)用得比較多,相關(guān)的網(wǎng)上資料也多,下面就大致介紹幾個(gè)。
對(duì)于 C++ 的程序員,強(qiáng)烈推薦使用 boost 提供的 circular_buffer 模板,該模板最開(kāi)始是在 boost 1.35 版本中引入的。鑒于 boost 在 C++ 社區(qū)中的地位,大伙兒應(yīng)該可以放心使用該模板。
對(duì)于 C 程序員,可以去看看開(kāi)源項(xiàng)目 circbuf ,不過(guò)該項(xiàng)目是 GPL 協(xié)議的,不太爽;而且活躍度不太高;而且只有一個(gè)開(kāi)發(fā)人員。大伙兒慎用!建議只拿它當(dāng)參考。
對(duì)于 C# 程序員,可以參考 CodeProject 上的一個(gè)示例 。
◇用于并發(fā)進(jìn)程
進(jìn)程間的環(huán)形緩沖區(qū),似乎少有現(xiàn)成的庫(kù)可用。大伙兒只好自己動(dòng)手、豐衣足食了。
適用于進(jìn)程間環(huán)形緩沖的 IPC 類型,常見(jiàn)的有 共享內(nèi)存 和文件。在這兩種方式上進(jìn)行環(huán)形緩沖,通常都采用數(shù)組的方式實(shí)現(xiàn)。程序事先分配好一個(gè)固定長(zhǎng)度的存儲(chǔ)空間,然后具體的讀寫操作、判斷 ” 空 ” 和 ” 滿 ” 、元素存儲(chǔ)等細(xì)節(jié)就可參照前面所說(shuō)的來(lái)進(jìn)行。
共享內(nèi)存方式的性能很好,適用于數(shù)據(jù)流量很大的場(chǎng)景。 但是有些語(yǔ)言(比如 Java )對(duì)于共享內(nèi)存不支持。因此,該方式在多語(yǔ)言協(xié)同開(kāi)發(fā)的系統(tǒng)中,會(huì)有一定的局限性。
而文件方式在編程語(yǔ)言方面支持很好,幾乎所有編程語(yǔ)言都支持操作文件。但它可能會(huì)受限于磁盤讀寫( Disk I/O )的性能。所以文件方式不太適合于快速數(shù)據(jù)傳輸;但是對(duì)于某些“ 數(shù)據(jù)單元 ” 很大的場(chǎng)合,文件方式是值得考慮的。
對(duì)于進(jìn)程間的環(huán)形緩沖區(qū),同樣要考慮好進(jìn)程間的同步、互斥等問(wèn)題,限于篇幅,此處就不細(xì)說(shuō)了。
下一個(gè)帖子 ,咱們來(lái)聊一下雙緩沖區(qū)的使用。
“ 雙緩沖區(qū) ” 是一個(gè)應(yīng)用很廣的手法。該手法用得最多的地方想必是屏幕繪制相關(guān)的領(lǐng)域(主要是為了減少屏幕閃爍)。另外,在設(shè)備驅(qū)動(dòng)和工控方面,雙緩沖也經(jīng)常被使用。不過(guò)今天要聊的,并不是針對(duì)上述的某個(gè)具體領(lǐng)域,而是側(cè)重于并發(fā)方面的同步 / 互斥開(kāi)銷。另外提醒一下,雙緩沖方式和前面提到的隊(duì)列緩沖、環(huán)形緩沖是可以結(jié)合使用滴。
★ 為啥要雙緩沖區(qū)
記得前幾天在 介紹隊(duì)列緩沖區(qū) 時(shí),提及了普通隊(duì)列緩沖區(qū)的兩個(gè)性能問(wèn)題: “ 內(nèi)存分配的開(kāi)銷 ” 和 “ 同步 / 互斥的開(kāi)銷 ” (健忘的同學(xué),先回去看看 那個(gè)帖子 復(fù)習(xí)一下)。 “ 內(nèi)存分配的開(kāi)銷 ” 已經(jīng)在 介紹環(huán)形緩沖區(qū) 的時(shí)候解決了,而今天要介紹的雙緩沖區(qū),就是沖著同步 / 互斥的開(kāi)銷來(lái)的。
為了防止有人給咱扣上 “ 過(guò)度設(shè)計(jì) ” 的大帽子,又得來(lái)一個(gè)事先聲明:只有當(dāng)同步或互斥的開(kāi)銷非常明顯的時(shí)候,你才應(yīng)該考慮雙緩沖區(qū)的使用。否則的話,大伙兒還是老老實(shí)實(shí)用最基本、最簡(jiǎn)單的隊(duì)列緩沖區(qū)吧。
★ 雙緩沖區(qū)的原理
前面說(shuō)了一通廢話,現(xiàn)在開(kāi)始切入正題,說(shuō)說(shuō)具體實(shí)現(xiàn)。
所謂 “ 雙緩沖區(qū) ” ,故名思義就是要有倆緩沖區(qū)(簡(jiǎn)稱 A 和 B )。這倆緩沖區(qū),總是一個(gè)用于生產(chǎn)者,另一個(gè)用于消費(fèi)者。當(dāng)倆緩沖區(qū)都操作完,再進(jìn)行一次切換(先前被生產(chǎn)者寫入的轉(zhuǎn)為消費(fèi)者讀出,先前消費(fèi)者讀取的轉(zhuǎn)為生產(chǎn)者寫入)。由于生產(chǎn)者和消費(fèi)者不會(huì) 同時(shí) 操作 同一個(gè) 緩沖區(qū)(不發(fā)生沖突),所以就不需要在讀寫 每一個(gè) 數(shù)據(jù)單元 的時(shí)候都進(jìn)行同步 / 互斥操作。順便提一下,這又一次展現(xiàn)了 空間換時(shí)間 的優(yōu)化思路。
但是光有倆緩沖區(qū)還不夠。為了真正做到 “ 不沖突 ” ,還得再搞兩個(gè)互斥鎖(簡(jiǎn)稱 La 和 Lb ),分別對(duì)應(yīng)倆緩沖區(qū)。生產(chǎn)者或消費(fèi)者如果要操作某個(gè)緩沖區(qū),必須先擁有對(duì)應(yīng)的互斥鎖。補(bǔ)充一句:要達(dá)到 “ 不沖突 ” 的效果,其實(shí)可以有多種搞法,今天只是挑一個(gè)簡(jiǎn)單的來(lái)聊。
★ 雙緩沖區(qū)的幾種狀態(tài)
為了加深某些同學(xué)的理解,再描述一下雙緩沖區(qū)的幾種狀態(tài)。
◇倆緩沖區(qū)都在使用的狀態(tài)(并發(fā)讀寫)
大多數(shù)情況下,生產(chǎn)者和消費(fèi)者都處于并發(fā)讀寫狀態(tài)。不妨設(shè)生產(chǎn)者寫入 A ,消費(fèi)者讀取 B 。在這種狀態(tài)下,生產(chǎn)者擁有鎖 La ;同樣的,消費(fèi)者擁有鎖 Lb 。由于倆緩沖區(qū)都是處于獨(dú)占狀態(tài),因此每次讀寫緩沖區(qū)中的元素( 數(shù)據(jù)單元 )都 不需要 再進(jìn)行加鎖、解鎖操作。這是節(jié)約開(kāi)銷的主要來(lái)源。
◇單個(gè)緩沖區(qū)空閑的狀態(tài)
由于兩個(gè)并發(fā)實(shí)體的速度會(huì)有差異,必然會(huì)出現(xiàn)一個(gè)緩沖區(qū)已經(jīng)操作完,而另一個(gè)尚未操作完。不妨假設(shè)生產(chǎn)者快于消費(fèi)者。
在這種情況下,當(dāng)生產(chǎn)者把 A 寫滿的時(shí)候,生產(chǎn)者要先釋放 La (表示它已經(jīng)不再操作 A ),然后嘗試獲取 Lb 。由于 B 還沒(méi)有被讀空, Lb 還被消費(fèi)者持有,所以生產(chǎn)者進(jìn)入發(fā)呆( Suspend )狀態(tài)。
◇緩沖區(qū)的切換
接著上面的話題。
過(guò)了若干時(shí)間,消費(fèi)者終于把 B 讀完。這時(shí)候,消費(fèi)者也要先釋放 Lb ,然后嘗試獲取 La 。由于 La 剛才已經(jīng)被生產(chǎn)者釋放,所以消費(fèi)者能立即擁有 La 并開(kāi)始讀取 A 的數(shù)據(jù)。而由于 Lb 被消費(fèi)者釋放,所以剛才發(fā)呆的生產(chǎn)者會(huì)緩過(guò)神來(lái)( Resume )并擁有 Lb ,然后生產(chǎn)者繼續(xù)往 B 寫入數(shù)據(jù)。
經(jīng)過(guò)上述幾個(gè)步驟,倆緩沖區(qū)完成了 對(duì)調(diào) ,變?yōu)椋荷a(chǎn)者寫入 B ,消費(fèi)者讀取 A 。
(對(duì)于雙緩沖,可引入序列號(hào)機(jī)制。)
★ 可能的并發(fā)問(wèn)題
本來(lái)單個(gè)緩沖區(qū)的生產(chǎn)者 / 消費(fèi)者問(wèn)題就已經(jīng)是教科書的經(jīng)典問(wèn)題了,現(xiàn)在搞出倆緩沖區(qū),所以就更加耗費(fèi)腦細(xì)胞了。一不小心,就會(huì)搞出些并發(fā)的 Bug ,而且并發(fā)的 Bug 還很難調(diào)試和測(cè)試(這也就是為啥不要輕易使用該玩意兒的原因)。
◇死鎖的問(wèn)題
假如把前面介紹的操作步驟調(diào)換一下順序:生產(chǎn)者或消費(fèi)者在操作完當(dāng)前的緩沖區(qū)之后,先去獲取另一個(gè)緩沖區(qū)的鎖,再來(lái)釋放當(dāng)前緩沖區(qū)的鎖。那會(huì)咋樣捏?
一旦兩個(gè)并發(fā)實(shí)體 同時(shí) 處理完各自緩沖區(qū),然后 同時(shí) 去獲取對(duì)方擁有的鎖,那就會(huì)出現(xiàn)典型的死鎖(死鎖的詳細(xì)解釋參見(jiàn) “ 這里 ” )場(chǎng)景。它倆從此陷入萬(wàn)劫不復(fù)的境地。
★ 應(yīng)用場(chǎng)景
介紹完并發(fā)問(wèn)題,按照 本系列 的慣例,最后再來(lái)介紹一下雙緩沖區(qū)在某些場(chǎng)合的應(yīng)用。
◇用于并發(fā)線程
在線程方式下,首先要考慮的是緩沖區(qū)的類型:到底用隊(duì)列方式還是環(huán)形方式。這方面的選擇依據(jù)在 介紹環(huán)形緩沖區(qū) 的時(shí)候已經(jīng)闡述過(guò)了,此處不再啰嗦(省去不少口水)。
另一個(gè)需要注意的是,某些編程語(yǔ)言或者程序庫(kù)提供了的線程安全的緩沖區(qū)(比如 JDK 1.5 引入的 ArrayBlockingQueue )。由于這種緩沖區(qū)會(huì)自動(dòng)為每次的讀寫進(jìn)行同步 / 互斥,所以就把雙緩沖的優(yōu)勢(shì)抵消掉了。因此,大伙兒在進(jìn)行緩沖區(qū)選型的時(shí)候要避開(kāi)這類緩沖區(qū)。
◇用于并發(fā)進(jìn)程
在進(jìn)程間使用雙緩沖,先得考察不同 IPC 類型的特點(diǎn)。由于今天討論雙緩沖的目的是降低同步 / 互斥的開(kāi)銷,對(duì)于那些已經(jīng)封裝了同步 / 互斥的 IPC 類型,就沒(méi)太大必要再去搞雙緩沖了(單憑這條就足以讓好多種 IPC 出局)。剩下的 IPC 類型中,比較適合用雙緩沖的主要是:共享內(nèi)存和文件。非常湊巧,這兩個(gè)玩意兒的特點(diǎn)和適用范圍在 環(huán)形緩沖區(qū)的帖子 里面也已經(jīng)介紹過(guò)了,俺又可以節(jié)省不少口水 :)
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
