小编典典

多个 goroutine 监听一个通道

go

我有多个 goroutine 试图同时在同一个频道上接收。似乎在通道上开始接收的最后一个 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 中已经过时(见评论)


阅读 329

收藏
2021-12-06

共1个答案

小编典典

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

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

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

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-routine,每个都写入了五次。主要的 go-routine 读取所有 25 条消息 - 您可能会注意到它们出现的顺序通常不是顺序的(即并发性很明显)。

这个例子演示了 Go 通道的一个特性:可以有多个作者共享一个通道;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()

第二个例子包括施加在主够程等待,否则退出及时并引起其他五个够程要提前终止(由于olov此校正)

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

2021-12-06