英文:
Is it safe to hide sending to channel behind function call
问题
我有一个名为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)
函数,它只是调用h.msgs <- msg
,我可以将其添加到HubInterface
中。作为一个Go
新手,我想知道,从并发的角度来看,这样做是否安全?如果安全的话,这是Go
中常见的方法吗?
Playground 在这里。
英文:
I have a struct called Hub
with a Run()
method which is executed in its own goroutine. This method sequentially handles incoming messages. Messages arrive concurrently from multiple producers (separate goroutines). Of course I use a channel
to accomplish this task. But now I want to hide the Hub
behind an interface
to be able to choose from its implementations. So, using a channel
as a simple Hub
's field isn't appropriate.
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)
}
So I've introduced Hub.SendMsg(msg string)
function that just calls h.msgs <- msg
and which I can add to the HubInterface
. And as a Go
-newbie I wonder, is it safe from the concurrency perspective? And if so - is it a common approach in Go
?
Playground here.
答案1
得分: 2
通道发送语义在将发送移入方法时不会改变。安德鲁的回答指出,通道需要使用make
函数创建才能成功发送,但无论发送是否在方法内部,这一点始终如此。
如果你担心确保调用者不会意外地得到一个带有nil
通道的无效Hub
实例,一种方法是将结构体类型设为私有(hub
),并使用一个NewHub()
函数返回一个完全初始化的hub
,并包装在你的接口类型中。由于结构体是私有的,其他包中的代码无法使用不完整的结构体字面值(或任何结构体字面值)来初始化它。
话虽如此,在Go语言中通常可以创建无效或荒谬的值,并且这是被接受的:net.IP("HELLO THERE BOB")
是有效的语法,或者net.IP{}
。因此,如果你认为公开你的Hub
类型更好,那就去做吧。
英文:
Channel send semantics do not change when you move the send into a method. Andrew's answer points out that the channel needs to be created with make
to send successfully, but that was always true, whether or not the send is inside a method.
If you are concerned about making sure callers can't accidentally wind up with invalid Hub
instances with a nil
channel, one approach is to make the struct type private (hub
) and have a NewHub()
function that returns a fully initialized hub
wrapped in your interface type. Since the struct is private, code in other packages can't try to initialize it with an incomplete struct literal (or any struct literal).
That said, it's often possible to create invalid or nonsense values in Go and that's accepted: net.IP("HELLO THERE BOB")
is valid syntax, or net.IP{}
. So if you think it's better to expose your Hub
type go ahead.
答案2
得分: -1
简单回答
是的
更好的回答
不是
通道非常适合从未知的Go协程中发出数据。它们可以安全地这样做,但我建议在某些部分要小心。在所列示的示例中,通道是由消费者创建结构体时创建的(而不是由消费者创建)。
假设消费者像下面这样创建了Hub:&Hub{}
。完全有效...除了SendMsg()
的所有调用都会永远阻塞。幸运的是,你将它们放在了自己的Go协程中。所以你还好,对吗?错了。你现在正在泄漏Go协程。似乎没问题...直到你运行一段时间。Go鼓励你拥有有效的零值。在这种情况下,&Hub{}
是无效的。
通过使用select{}
可以确保SendMsg()
不会阻塞,但是你必须决定在遇到默认情况时要做什么(例如,丢弃数据)。通道可能会因为不正确的设置而阻塞。假设以后你不仅仅是在从通道中读取数据后打印数据。如果读取变得非常缓慢,或者在IO上阻塞,会发生什么情况?那么你将开始对生产者施加压力。
最终,通道允许你不用太多考虑并发性...但是如果这是一个高吞吐量的东西,那么你需要考虑很多。如果这是生产代码,那么你需要了解你的API中涉及到SendMsg()
阻塞的情况。
英文:
Easy answer
Yes
Better answer
No
Channels are great for emitting data from unknown go-routines. They do so safely, however I would recommend being careful with a few parts. In the listed example the channel is created with the construction of the struct by the consumer (and not not by a consumer).
Say the consumer creates the Hub like the following: &Hub{}
. Perfectly valid... Apart from the fact that all the invokes of SendMsg()
will block for forever. Luckily you placed those in their own go-routines. So you're still fine right? Wrong. You are now leaking go-routines. Seems fine... until you run this for a period of time. Go encourages you to have valid zero values. In this case &Hub{}
is not valid.
Ensuring SendMsg()
won't block could be achieved via a select{}
however you then have to decide what to do when you encounter the default case (e.g. throw data away). The channel could block for more reasons than bad setup too. Say later you do more than simply print the data after reading from the channel. What if the read gets very slow, or blocks on IO. You then will start pushing back on the producers.
Ultimately, channels allow you to not think much about concurrency... However if this is something of high-throughput, then you have quite a bit to consider. If it is production code, then you need to understand that your API here involves SendMsg()
blocking.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论