在go中使用linked channels進行數據廣播

在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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章