隐藏发送到函数调用后面的通道是否安全
Is it safe to hide sending to channel behind function call
我有一个名为 Hub
的结构,它带有一个 Run()
方法,该方法在其自己的 goroutine 中执行。此方法按顺序处理传入的消息。消息从多个生产者(单独的 goroutines)同时到达。当然我用了一个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)
函数,它只调用 h.msgs <- msg
并且我可以将其添加到 HubInterface
。作为一个 Go
-newbie 我想知道,从并发的角度来看它是否安全?如果是这样 - 这是 Go
中的常见方法吗?
游乐场here.
简单回答
是
更好的回答
否
通道非常适合从未知的 go-routines 发出数据。他们这样做是安全的,但我会建议小心一些部分。在列出的示例中,通道是由消费者(而不是消费者)构造结构创建的。
假设消费者像下面这样创建 Hub:&Hub{}
。完全有效...除了 SendMsg()
的所有调用将永远阻塞这一事实。幸运的是,您将它们放在了它们自己的 go-routines 中。所以你还好吧?错误的。你现在正在泄漏 go-routines。似乎很好...直到你 运行 这一段时间。 Go 鼓励您使用有效的零值。在这种情况下 &Hub{}
无效。
确保 SendMsg()
不会阻塞可以通过 select{}
实现,但是您必须决定在遇到默认情况时要做什么(例如,丢弃数据)。除了设置不当之外,频道可能会因更多原因而阻塞。稍后再说,您所做的不仅仅是在从通道读取数据后打印数据。如果读取变得非常慢,或者 IO 阻塞怎么办。然后你将开始反击生产者。
归根结底,channels 让你不用考虑太多的并发问题...但是如果这是高吞吐量的东西,那么你就需要考虑很多了。如果它是生产代码,那么你需要了解你的 API 这里涉及 SendMsg()
阻塞。
当您将发送移动到方法中时,通道发送语义不会改变。安德鲁的回答指出,需要使用 make
创建通道才能成功发送,但这始终是正确的,无论发送是否在方法内部。
如果您担心调用者不会意外结束具有 nil
通道的无效 Hub
实例,一种方法是将结构类型设为私有(hub
) 并有一个 NewHub()
函数,returns 一个完全初始化的 hub
包装在你的接口类型中。由于该结构是私有的,因此其他包中的代码无法尝试使用不完整的结构文字(或任何结构文字)对其进行初始化。
也就是说,经常 可能在 Go 中创建无效或无意义的值,这是可以接受的:net.IP("HELLO THERE BOB")
是有效语法,或 net.IP{}
。因此,如果您认为公开您的 Hub
类型更好,请继续。
我有一个名为 Hub
的结构,它带有一个 Run()
方法,该方法在其自己的 goroutine 中执行。此方法按顺序处理传入的消息。消息从多个生产者(单独的 goroutines)同时到达。当然我用了一个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)
函数,它只调用 h.msgs <- msg
并且我可以将其添加到 HubInterface
。作为一个 Go
-newbie 我想知道,从并发的角度来看它是否安全?如果是这样 - 这是 Go
中的常见方法吗?
游乐场here.
简单回答
是
更好的回答
否
通道非常适合从未知的 go-routines 发出数据。他们这样做是安全的,但我会建议小心一些部分。在列出的示例中,通道是由消费者(而不是消费者)构造结构创建的。
假设消费者像下面这样创建 Hub:&Hub{}
。完全有效...除了 SendMsg()
的所有调用将永远阻塞这一事实。幸运的是,您将它们放在了它们自己的 go-routines 中。所以你还好吧?错误的。你现在正在泄漏 go-routines。似乎很好...直到你 运行 这一段时间。 Go 鼓励您使用有效的零值。在这种情况下 &Hub{}
无效。
确保 SendMsg()
不会阻塞可以通过 select{}
实现,但是您必须决定在遇到默认情况时要做什么(例如,丢弃数据)。除了设置不当之外,频道可能会因更多原因而阻塞。稍后再说,您所做的不仅仅是在从通道读取数据后打印数据。如果读取变得非常慢,或者 IO 阻塞怎么办。然后你将开始反击生产者。
归根结底,channels 让你不用考虑太多的并发问题...但是如果这是高吞吐量的东西,那么你就需要考虑很多了。如果它是生产代码,那么你需要了解你的 API 这里涉及 SendMsg()
阻塞。
当您将发送移动到方法中时,通道发送语义不会改变。安德鲁的回答指出,需要使用 make
创建通道才能成功发送,但这始终是正确的,无论发送是否在方法内部。
如果您担心调用者不会意外结束具有 nil
通道的无效 Hub
实例,一种方法是将结构类型设为私有(hub
) 并有一个 NewHub()
函数,returns 一个完全初始化的 hub
包装在你的接口类型中。由于该结构是私有的,因此其他包中的代码无法尝试使用不完整的结构文字(或任何结构文字)对其进行初始化。
也就是说,经常 可能在 Go 中创建无效或无意义的值,这是可以接受的:net.IP("HELLO THERE BOB")
是有效语法,或 net.IP{}
。因此,如果您认为公开您的 Hub
类型更好,请继续。