《游戲編程模式》筆記——事件隊列
意圖
解耦發(fā)出消息或事件的時間和處理它的時間。
模式
事件隊列在隊列中按先入先出的順序存儲一系列通知或請求。發(fā)送通知時,將請求放入隊列兵返回。處理請求的系統(tǒng)之后稍晚從隊列中獲取請求并處理。這解耦了發(fā)送者和接受者,既靜態(tài)又及時。
何時使用
如果只想解耦接受者和發(fā)送者,觀察者模式和命令模式都可以用較小的復(fù)雜度進(jìn)行處理。
在解耦某些需要及時處理的東西時使用隊列。
隊列將控制權(quán)交給了發(fā)送者,接受者可以延遲處理,合并或者忽視請求。發(fā)送者能做的就是向隊列發(fā)送請求,當(dāng)發(fā)送者需要回復(fù)時,隊列不是好選擇。
設(shè)計決策
隊列中存儲了什么?
如果存儲事件:
“事件”或者“通知”描繪已經(jīng)發(fā)生的事情。入隊事件,其他對象可以對這個事件作出回應(yīng)。
可能會允許多個監(jiān)聽者。隊列包含的是已經(jīng)發(fā)生的事情,發(fā)送者可能不關(guān)心誰接受它。
訪問隊列的模塊更廣。事件隊列通常廣播事件到任何感興趣的部分,為了最大程度允許哪些部分能感興趣,隊列一般是全局可見的。
如果存儲消息:
“消息”或“請求”描繪了想要發(fā)生在未來的事情,比如“播放聲音”。可以視其為服務(wù)的異步API。
更可能只有一個監(jiān)聽者。假如存儲的消息只請求音頻API播放聲音,如果引擎的隨便一個地方都能從隊列中拿走消息就不好了。
誰能從隊列中讀取?
單播隊列:
在隊列是類API的一部分時,單播是自然的。
隊列變成讀取者的實現(xiàn)細(xì)節(jié)。發(fā)送者知道的所有事就是發(fā)條信息。
隊列更封裝。
不用擔(dān)心監(jiān)聽者之間的競爭。使用多個監(jiān)聽者,就需要決定隊列中的事物是一對多的分給全部監(jiān)聽者,還是一對一分給單獨的監(jiān)聽者。這兩種情況下,監(jiān)聽者最終要么做了多余的事要么在互相干擾。使用單一監(jiān)聽者這種復(fù)雜性就消失了。
廣播隊列:
這是大多數(shù)“事件”系統(tǒng)工作的方法。一個事件進(jìn)來,所有監(jiān)聽者都能收到這個事件。
但事件也可能無人接收。零個監(jiān)聽者時,事件就會消失。
也許需要過濾事件。廣播隊列對程序的所有部分可見,大多數(shù)廣播事件讓監(jiān)聽者篩出其需要接收的事件。
工作隊列:
類似廣播隊列,有多個監(jiān)聽器。不同之處在于每個隊列的東西只會投到監(jiān)聽器其中的一個。常用于將工作打包給同時運(yùn)行的線程池。
但是需要規(guī)劃。由于一個事物只有一個監(jiān)聽器,隊列邏輯需要指出最好的選項??梢韵駌ound robin算法或者亂序選擇一樣簡單,或者可以使用更加復(fù)雜的優(yōu)先度系統(tǒng)。
誰能寫入隊列?
隊列模式兼容所有可能的讀寫設(shè)置:一對一、一對多、多對一、多對多。
使用單個寫入器:
這種風(fēng)格和同步的觀察者模式很像,有特定對象收集所有可接受的事件。
隱式知道事件是從哪來的。由于只有一個對象可以向隊列添加事件,任何監(jiān)聽器都可以圈圈的假設(shè)那就是發(fā)送者。
通常運(yùn)行有多個讀取者。可以使用單發(fā)送者對單接收者的隊列,但這樣溝通系統(tǒng)更像純粹的隊列數(shù)據(jù)結(jié)構(gòu)。
使用多個寫入器:
代碼的任何部分都能給隊列添加請求,“全局”或“中心”事件總線像這樣工作。
要小心環(huán)路。可能會在處理事件時添加事件,導(dǎo)致觸發(fā)反饋循環(huán)。
很可能需要在事件中添加對發(fā)送者的引用。監(jiān)聽者接到事件時不知道是誰發(fā)送的,如果需要知道發(fā)送者,需要將發(fā)送者打包到事件對象中。
對象在隊列中的生命周期如何?
傳遞所有權(quán):
手動管理內(nèi)存的傳統(tǒng)方式。消息入隊時,隊列擁有了它,發(fā)送者不再擁有它。當(dāng)它被處理時,接受者獲取了所有權(quán),負(fù)責(zé)銷毀它。
共享所有權(quán):
對于擁有垃圾回收機(jī)制的語言,消息只要有地方引用就會存在,沒有引用時自動釋放。
隊列擁有它:
讓消息永遠(yuǎn)存在隊列中。發(fā)送者不再自己分配消息內(nèi)存,隊列返回一個隊列中已經(jīng)在內(nèi)存的消息的引用,接收者這引用隊列中相同的消息。換言之,隊列存儲的背后是對象池模式。
隊列模式是觀察者模式的異步實現(xiàn)。
消息隊列是隊列模式的更高級實現(xiàn),事件隊列在應(yīng)用中,消息隊列通常在應(yīng)用間交流。
很像GoF的狀態(tài)模式中的確定狀態(tài)機(jī),需要一個輸入流。如果想要異步響應(yīng)??梢钥紤]隊列存儲它們。當(dāng)一對狀態(tài)機(jī)相互發(fā)送消息時,每個狀態(tài)機(jī)都有一個小的未處理隊列,需要重新發(fā)明actor model。
Go語言內(nèi)建的“通道”類型本質(zhì)上是事件隊列或消息隊列。
參考
《游戲編程模式》