隐藏发送到函数调用后面的通道是否安全

时间:2016-11-12 18:38:29

标签: go concurrency channel goroutine

我有一个名为Hub的结构,其Run()方法在自己的goroutine中执行。此方法顺序处理传入的消息。消息从多个生产者(单独的goroutine)同时到达。当然我使用channel来完成这项任务。但现在我想隐藏Hub后面的interface,以便能够从其实现中进行选择。因此,使用channel作为简单的Hub字段是不合适的。

package main
import "fmt"
import "time"

type Hub struct {
    msgs chan string
}
func (h *Hub) Run() {
    for {
        msg, hasMore := <- h.msgs
        if !hasMore {
            return
        }
        fmt.Println("hub: msg received", msg)
    }
}
func (h *Hub) SendMsg(msg string) {
    h.msgs <- msg
}

func send(h *Hub, prefix string) {
    for i := 0; i < 5; i++ {
        fmt.Println("main: sending msg")
        h.SendMsg(fmt.Sprintf("%s %d", prefix, i))
    }
}

func main() {
    h := &Hub{make(chan string)}
    go h.Run()
    for i := 0; i < 10; i++ {
        go send(h, fmt.Sprintf("msg sender #%d", i))
    }
    time.Sleep(time.Second)
}

所以我介绍了只调用Hub.SendMsg(msg string)的{​​{1}}函数,我可以将其添加到h.msgs <- msg。作为一个HubInterface新手,我想知道,从并发角度看它是否安全?如果是这样 - 它是Go中的常见方法吗?

游乐场here

2 个答案:

答案 0 :(得分:2)

将发送移动到方法中时,通道发送语义不会更改。安德鲁的回答指出,需要使用make创建频道才能成功发送,但无论发送是否在方法内,这都是正确的。

如果您担心确保调用者不会意外地使用Hub通道的无效nil实例,则一种方法是将结构类型设为私有(hub)并且有一个NewHub()函数,它返回一个包含在你的接口类型中的完全初始化的hub。由于struct是私有的,因此其他包中的代码不能尝试使用不完整的struct literal(或任何struct literal)来初始化它。

也就是说,经常可以在Go中创建无效或无意义的值并且已被接受:net.IP("HELLO THERE BOB")是有效的语法,或net.IP{}。因此,如果您认为最好公开Hub类型,请继续。

答案 1 :(得分:-1)

简单回答

更好的回答

没有

频道非常适合从未知的常规程序中发出数据。他们安全地这样做,但我建议小心一些部分。在列出的示例中,通道由消费者构建结构(而不​​是消费者)。

假设消费者创建Hub,如下所示:&Hub{}。完全有效...除了SendMsg()的所有调用将永远阻止的事实。幸运的是,你把它们放在自己的常规中。所以你还好吗?错误。你现在正在泄漏惯例。看起来很好......直到你运行一段时间。 Go鼓励您拥有有效的零值。在这种情况下,&Hub{}无效。

确保SendMsg()不会阻止可以通过select{}实现,但是当您遇到默认情况时(例如,抛弃数据),您必须决定该做什么。由于设置错误,通道可能会阻塞更多原因。稍后再说,您不仅仅是在从频道阅读后打印数据。如果读取速度很慢或IO上的阻塞会怎么样?然后你将开始推回生产者。

最终,通道允许您不要太多考虑并发性......但是如果这是高吞吐量的话,那么您需要考虑很多。如果是生产代码,那么您需要了解此处的API涉及SendMsg()阻止。