【问题标题】:Is it safe to hide sending to channel behind function call在函数调用后面隐藏发送到通道是否安全
【发布时间】:2017-03-26 18:23:29
【问题描述】:

我有一个名为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 &lt;- msg,我可以将它添加到HubInterface。作为Go-newbie,我想知道,从并发的角度来看它是否安全?如果是这样 - 这是Go 中的常用方法吗?

游乐场here.

【问题讨论】:

  • 是的。我不知道该怎么回答,因为我不确定什么会让你相信它可能不安全。
  • @JimB,实际上,在思考了它在后台是如何工作的之后,我意识到答案是非常明显的,因为写入通道是线程安全的。
  • 单个通道可用于发送语句、接收操作以及由任意数量的 goroutine 调用内置函数 cap 和 len,而无需进一步同步。 ,见If I am using channels properly should I need to use mutexes?

标签: go concurrency channel goroutine


【解决方案1】:

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

如果您担心确保调用者不会意外以 nil 通道的无效 Hub 实例结束,一种方法是将结构类型设为私有 (hub) 并使用 NewHub()返回包装在您的接口类型中的完全初始化的hub 的函数。由于结构是私有的,其他包中的代码无法尝试使用不完整的结构字面量(或任何结构字面量)对其进行初始化。

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

【讨论】:

    【解决方案2】:

    简单回答

    是的

    更好的答案

    没有

    Channel 非常适合从未知的 go-routines 发出数据。他们这样做是安全的,但是我建议小心一些部分。在列出的示例中,通道是由消费者(而不是消费者)通过构造结构创建的。

    假设消费者创建集线器,如下所示:&amp;Hub{}。完全有效......除了SendMsg() 的所有调用将永远阻塞。幸运的是,您将它们放在了他们自己的 go-routines 中。那你还是没事吧?错误的。您现在正在泄漏 go-routines。似乎很好......直到你运行一段时间。 Go 鼓励你使用有效的零值。在这种情况下,&amp;Hub{} 无效。

    确保SendMsg() 不会阻塞可以通过select{} 实现,但是您必须决定在遇到默认情况时该怎么做(例如丢弃数据)。通道可能会因为设置错误而阻塞更多的原因。稍后说,您所做的不仅仅是在从通道读取数据后打印数据。如果读取变得非常慢,或者 IO 阻塞怎么办。然后,您将开始反击制作人。

    最终,通道让您不必过多考虑并发性……但是,如果这是高吞吐量的事情,那么您需要考虑很多。如果是生产代码,那么你需要明白你这里的API涉及到SendMsg()阻塞。

    【讨论】:

    • 这很令人困惑;提出的问题是通道发送语义是否通过将发送移动到函数中而改变,而事实并非如此。 &amp;Hub{} 是句号类型的无效值,无论通道发送是否在函数调用中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-23
    • 1970-01-01
    • 2021-07-27
    • 2017-04-25
    • 2015-09-27
    • 1970-01-01
    相关资源
    最近更新 更多