我有一个名为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。
答案 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()
阻止。