多个 goroutines 在一个频道上监听

新手上路,请多包涵

我有多个 goroutines 试图同时在同一个频道上接收。似乎最后一个开始在通道上接收的 goroutine 获得了价值。这是语言规范中的某个地方还是未定义的行为?

 c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

输出:

 goroutine 4

操场上的例子

编辑:

我才意识到这比我想象的要复杂。消息在所有 goroutine 之间传递。

 c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

输出:

 original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

注意: 以上输出在较新版本的 Go 中已过时(请参阅评论)

操场上的例子

原文由 Ilia Choly 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 506
2 个回答

是的,这很复杂,但是有一些经验法则应该会让事情变得更简单。

  • 更喜欢对传递给 go-routines 的通道使用正式参数, 而不是在全局范围内访问通道。您可以通过这种方式获得更多的编译器检查,以及更好的模块化。
  • 避免在特定的 go-routine(包括“主要”例程)中在同一通道上进行读写。否则,死锁的风险要大得多。

这是您的程序的替代版本,应用了这两个准则。这个案例展示了一个频道上的许多作者和一个读者:

 c := make(chan string)

for i := 1; i <= 5; i++ {
    go func(i int, co chan<- string) {
        for j := 1; j <= 5; j++ {
            co <- fmt.Sprintf("hi from %d.%d", i, j)
        }
    }(i, c)
}

for i := 1; i <= 25; i++ {
    fmt.Println(<-c)
}

http://play.golang.org/p/quQn7xePLw

它创建了五个写入单个通道的 go-routines,每个 go-routines 写入五次。主要的 go-routine 读取所有 25 条消息——你可能会注意到它们出现的顺序通常不是连续的(即并发是显而易见的)。

这个例子演示了 Go channels 的一个特性:可以让多个作者共享一个 channel; Go 将自动交错消息。

这同样适用于一个频道上的一位作者和多个读者,如此处的第二个示例所示:

 c := make(chan int)
var w sync.WaitGroup
w.Add(5)

for i := 1; i <= 5; i++ {
    go func(i int, ci <-chan int) {
        j := 1
        for v := range ci {
            time.Sleep(time.Millisecond)
            fmt.Printf("%d.%d got %d\n", i, j, v)
            j += 1
        }
        w.Done()
    }(i, c)
}

for i := 1; i <= 25; i++ {
    c <- i
}
close(c)
w.Wait()

第二个示例 包括对主 goroutine 施加的等待,否则它会立即退出并导致其他五个 goroutine 提前终止 (感谢 olov 进行此更正)

在这两个示例中,都不需要缓冲。将缓冲仅视为性能增强器通常是一个很好的原则。如果您的程序在 没有 缓冲区的情况下不会死锁,那么它也不会 在有 缓冲区的情况下死锁(但反之 并不 总是正确的)。所以,作为 另一个经验法则,开始时不要缓冲,然后根据需要添加它

原文由 Rick-777 发布,翻译遵循 CC BY-SA 3.0 许可协议

回复晚了,但我希望这对以后的其他人有帮助,比如 长轮询、“全局”按钮、向所有人广播?

Effective Go 解释了这个问题:

接收器总是阻塞,直到有数据要接收。

这意味着您不能让超过 1 个 goroutine 监听 1 个通道并期望所有 goroutines 接收相同的值。

运行此 代码示例

 package main

import "fmt"

func main() {
    c := make(chan int)

    for i := 1; i <= 5; i++ {
        go func(i int) {
        for v := range c {
                fmt.Printf("count %d from goroutine #%d\n", v, i)
            }
        }(i)
    }

    for i := 1; i <= 25; i++ {
        c<-i
    }

    close(c)
}

即使有 5 个 goroutines 正在监听该频道,您也不会多次看到“count 1”。这是因为当第一个 goroutine 阻塞通道时,所有其他 goroutine 必须排队等待。当通道被解锁时,计数已经被接收并从通道中删除,因此下一个 goroutine 将获得下一个计数值。

原文由 Brenden 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题