在go中使用linked channels進行數據廣播
原文在這裏(需翻牆),爲啥想要翻譯這篇文章是因爲在實際中也碰到過如此的問題,而該文章的解決方式很巧妙,希望對大家有用。
在go中channels是一個很強大的東西,但是在處理某些事情上面還是有侷限的。其中之一就是一對多的通信。channels在多個writer,一個reader的模型下面工作的很好,但是卻不能很容易的處理多個reader等待獲取一個writer發送的數據的情況。
處理這樣的情況,可能的一個go api原型如下:
type Broadcaster …
func NewBroadcaster() Broadcaster
func (b Broadcaster) Write(v interface{})
func (b Broadcaster) Listen() chan interface{}
broadcast channel通過NewBroadcaster創建,通過Write函數進行數據廣播。爲了監聽這個channel的信息,我們使用Listen,該函數返回一個新的channel去接受Write發送的數據。
這套解決方案需要一箇中間process用來處理所有reader的註冊。當調用Listen創建新的channel之後,該channel就被註冊,通常該中間process的主循環如下:
for {
select {
case v := <-inc:
for _, c := range(listeners) {
c <- v
}
case c := <- registeryc:
listeners.push(c)
}
}
這是一個通常的做法,(譯者也經常這麼幹)。但是該process在處理數據廣播的時候會阻塞,直到所有的readers讀取到值。一個可選的解決方式就是reader的channel是有buffer緩衝的,緩衝大小我們可以按需調節。或者當buffer滿的時候我們將數據丟棄。
但是這篇blog並不是介紹上面這套做法的。這篇blog主要提出了另一種實現方式用來實現writer永遠不阻塞,一個慢的reader並不會因爲writer發送數據太快而要考慮分配太大的buffer。
雖然這麼做不會有太高的性能,但是我並不在意,因爲我覺得它很酷。我相信我會找到一個很好的使用地方的。
首先是核心的東西:
type broadcast struct {
c chan broadcast
v interface{}
}
這就是我說的linked channel,但是其實是Ouroboros data structure。也就是,這個struct實例在發送到channel的時候包含了自己。
從另一方面來說,如果我有一個chan broadcast類型的數據,那麼我就能從中讀取一個broadcast b,b.v就是writer發送的任意數據,而b.c,這個原先的chan broadcast,則能夠讓我重複這個過程。
另一個可能讓人困惑的地方在於一個帶有緩衝區的channel能夠被用來當做一個1對多廣播的對象。如果我定義如下的buffered channel:
var c = make(chan T, 1)
任何試圖讀取c的process都將阻塞直到有數據寫入。
當我們想廣播一個數據的時候,我們只是簡單的將其寫入這個channel,這個值只會被一個reader給獲取,但是我們約定,只要讀取到了數據,我們立刻將其再次放入該channel,如下:
func wait(c chan T) T {
v := <-c
c <-v
return v
}
結合上面兩個討論的東西,我們就能夠發現如果在broadcast struct裏面的channel如果能夠按照上面的方式進行處理,我們就能實現一個數據廣播。
代碼如下:
package broadcast
type broadcast struct {
c chan broadcast;
v interface{};
}
type Broadcaster struct {
// private fields:
Listenc chan chan (chan broadcast);
Sendc chan<- interface{};
}
type Receiver struct {
// private fields:
C chan broadcast;
}
// create a new broadcaster object.
func NewBroadcaster() Broadcaster {
listenc := make(chan (chan (chan broadcast)));
sendc := make(chan interface{});
go func() {
currc := make(chan broadcast, 1);
for {
select {
case v := <-sendc:
if v == nil {
currc <- broadcast{};
return;
}
c := make(chan broadcast, 1);
b := broadcast{c: c, v: v};
currc <- b;
currc = c;
case r := <-listenc:
r <- currc
}
}
}();
return Broadcaster{
Listenc: listenc,
Sendc: sendc,
};
}
// start listening to the broadcasts.
func (b Broadcaster) Listen() Receiver {
c := make(chan chan broadcast, 0);
b.Listenc <- c;
return Receiver{<-c};
}
// broadcast a value to all listeners.
func (b Broadcaster) Write(v interface{}) { b.Sendc <- v }
// read a value that has been broadcast,
// waiting until one is available if necessary.
func (r *Receiver) Read() interface{} {
b := <-r.C;
v := b.v;
r.C <- b;
r.C = b.c;
return v;
}
下面就是譯者的東西了,這套方式實現的很巧妙,首先它解決了reader register以及unregister的問題。其次,我覺得它很好的使用了流式化處理的方式,當reader讀取到了一個值,reader可以將其傳遞給下一個reader繼續使用,同時自己在開始監聽下一個新的值的到來。
譯者自己的一個測試用例:
func TestBroadcast(t *testing.T) {
b := NewBroadcaster()
r := b.Listen()
b.Write("hello")
if r.Read().(string) != "hello" {
t.Fatal("error string")
}
r1 := b.Listen()
b.Write(123)
if r.Read().(int) != 123 {
t.Fatal("error int")
}
if r1.Read().(int) != 123 {
t.Fatal("error int")
}
b.Write(nil)
if r.Read() != nil {
t.Fatal("error nit")
}
if r1.Read() != nil {
t.Fatal("error nit")
}
}
當然,這套方式還有點不足,主要就在於Receiver Read函數,並不能很好的與select進行整合,具體可以參考該作者另一篇bloghttp://rogpeppe.wordpress.com/2010/01/04/select-functions-for-go/。