通过Go中的多个频道广播频道

时间:2016-07-15 16:53:10

标签: go channel

我想将从频道收到的数据广播到频道列表。通道列表是动态的,可以在运行阶段进行修改。

作为Go中的新开发者,我编写了这段代码。我觉得它很重要我想要的东西。有更好的方法吗?

package utils

import "sync"

// StringChannelBroadcaster broadcasts string data from a channel to multiple channels
type StringChannelBroadcaster struct {
    Source      chan string
    Subscribers map[string]*StringChannelSubscriber
    stopChannel chan bool
    mutex       sync.Mutex
    capacity    uint64
}

// NewStringChannelBroadcaster creates a StringChannelBroadcaster
func NewStringChannelBroadcaster(capacity uint64) (b *StringChannelBroadcaster) {
    return &StringChannelBroadcaster{
        Source:      make(chan string, capacity),
        Subscribers: make(map[string]*StringChannelSubscriber),
        capacity:    capacity,
    }
}

// Dispatch starts dispatching message
func (b *StringChannelBroadcaster) Dispatch() {
    b.stopChannel = make(chan bool)
    for {
        select {
        case val, ok := <-b.Source:
            if ok {
                b.mutex.Lock()
                for _, value := range b.Subscribers {
                    value.Channel <- val
                }
                b.mutex.Unlock()
            }
        case <-b.stopChannel:
            return
        }
    }
}

// Stop stops the Broadcaster
func (b *StringChannelBroadcaster) Stop() {
    close(b.stopChannel)
}

// StringChannelSubscriber defines a subscriber to a StringChannelBroadcaster
type StringChannelSubscriber struct {
    Key     string
    Channel chan string
}

// NewSubscriber returns a new subsriber to the StringChannelBroadcaster
func (b *StringChannelBroadcaster) NewSubscriber() *StringChannelSubscriber {
    key := RandString(20)
    newSubscriber := StringChannelSubscriber{
        Key:     key,
        Channel: make(chan string, b.capacity),
    }
    b.mutex.Lock()
    b.Subscribers[key] = &newSubscriber
    b.mutex.Unlock()

    return &newSubscriber
}

// RemoveSubscriber removes a subscrber from the StringChannelBroadcaster
func (b *StringChannelBroadcaster) RemoveSubscriber(subscriber *StringChannelSubscriber) {
    b.mutex.Lock()
    delete(b.Subscribers, subscriber.Key)
    b.mutex.Unlock()
}

谢谢,

于连

1 个答案:

答案 0 :(得分:1)

我认为你可以稍微简化一下:摆脱stopChannelStop方法。你可以关闭Source而不是调用Stop,并在Dispatch中检测到(ok将为false)退出(实际上你只能在源通道的范围内)。

您可以摆脱Dispatch,只需在NewStringChannelBroadcaster中使用for循环启动goroutine,因此外部代码不必单独启动调度周期。

您可以使用通道类型作为地图键,因此您的地图可以变为map[chan string]struct{}(空结构,因为您不需要地图值)。因此,您的NewSubscriber可以获取频道类型参数(或创建新频道并将其返回),并将其插入到地图中,您不需要随机字符串或StringChannelSubscriber类型。

我也做了一些改进,比如关闭用户频道:

package main

import "sync"

import (
    "fmt"
    "time"
)

// StringChannelBroadcaster broadcasts string data from a channel to multiple channels
type StringChannelBroadcaster struct {
    Source      chan string
    Subscribers map[chan string]struct{}
    mutex       sync.Mutex
    capacity    uint64
}

// NewStringChannelBroadcaster creates a StringChannelBroadcaster
func NewStringChannelBroadcaster(capacity uint64) *StringChannelBroadcaster {
    b := &StringChannelBroadcaster{
        Source:      make(chan string, capacity),
        Subscribers: make(map[chan string]struct{}),
        capacity:    capacity,
    }
    go b.dispatch()
    return b
}

// Dispatch starts dispatching message
func (b *StringChannelBroadcaster) dispatch() {
    // for iterates until the channel is closed
    for val := range b.Source {
        b.mutex.Lock()
        for ch := range b.Subscribers {
            ch <- val
        }
        b.mutex.Unlock()
    }
    b.mutex.Lock()
    for ch := range b.Subscribers {
        close(ch)
        // you shouldn't be calling RemoveSubscriber after closing b.Source
        // but it's better to be safe than sorry
        delete(b.Subscribers, ch)
    }
    b.Subscribers = nil
    b.mutex.Unlock()
}

func (b *StringChannelBroadcaster) NewSubscriber() chan string {
    ch := make(chan string, b.capacity)
    b.mutex.Lock()
    if b.Subscribers == nil {
        panic(fmt.Errorf("NewSubscriber called on closed broadcaster"))
    }
    b.Subscribers[ch] = struct{}{}
    b.mutex.Unlock()

    return ch
}

// RemoveSubscriber removes a subscrber from the StringChannelBroadcaster
func (b *StringChannelBroadcaster) RemoveSubscriber(ch chan string) {
    b.mutex.Lock()
    if _, ok := b.Subscribers[ch]; ok {
        close(ch)                 // this line does have to be inside the if to prevent close of closed channel, in case RemoveSubscriber is called twice on the same channel
        delete(b.Subscribers, ch) // this line doesn't need to be inside the if
    }
    b.mutex.Unlock()
}

func main() {
    b := NewStringChannelBroadcaster(0)

    var toberemoved chan string

    for i := 0; i < 3; i++ {
        i := i

        ch := b.NewSubscriber()
        if i == 1 {
            toberemoved = ch
        }
        go func() {
            for v := range ch {
                fmt.Printf("receive %v: %v\n", i, v)
            }
            fmt.Printf("Exit %v\n", i)
        }()
    }

    b.Source <- "Test 1"
    b.Source <- "Test 2"
    // This is a race condition: the second reader may or may not receive the first two messages.
    b.RemoveSubscriber(toberemoved)
    b.Source <- "Test 3"

    // let the reader goroutines receive the last message
    time.Sleep(2 * time.Second)

    close(b.Source)

    // let the reader goroutines write close message
    time.Sleep(1 * time.Second)
}

https://play.golang.org/p/X-NcikvbDM

编辑:我在关闭RemoveSubscriber后调用Source时添加了你的编辑来修复恐慌,但你不应该这样做,你应该让结构及其中的所有内容都是垃圾频道关闭后收集。 如果在关闭NewSubscriber之后调用它,我还会对Source添加一个恐慌。以前你可以做到这一点,它会泄漏创建的频道,并且可能是会在该频道上永久封锁的goroutine。

如果你可以在一个已经关闭的广播公司上拨打NewSubscriber(或RemoveSubscriber),这可能意味着你的代码在某个地方出现了错误,因为你正在抓住一个你不应该做的广播公司。是的。