多个goroutines在一个频道上收听

时间:2013-03-30 06:14:11

标签: go

我有多个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

Example On Playground

修改

我刚才意识到它比我想象的要复杂得多。消息传递给所有goroutines。

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

Example On Playground

5 个答案:

答案 0 :(得分:49)

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

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

以下是您的计划的替代版本,应用这两个指南。这个案例展示了许多作家和作家。一个频道的读者:

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通道的一项功能:可以让多个编写器共享一个通道; 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()

second example包括对主goroutine的等待,否则将立即退出并导致其他五个goroutine提前终止(感谢olov进行此更正)

在两个示例中,都不需要缓冲。将缓冲视为仅性能增强器通常是一个很好的原则。如果您的程序没有没有缓冲区死锁,它也不会使用缓冲区死锁(但是相反的不是总是为真)。因此,作为另一条经验法则,请在没有缓冲的情况下启动,然后根据需要添加

答案 1 :(得分:14)

迟到的回复,但我希望将来会像Long Polling, "Global" Button, Broadcast to everyone?

那样帮助其他人

Effective Go解释了这个问题:

  

接收器始终会阻塞,直到有数据要接收为止。

这意味着你不能有超过1个goroutine监听1个频道,并期望所有goroutine都能收到相同的值。

运行此Code Example

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正在收听该频道,您也不会多次看到“计数1”。这是因为当第一个goroutine阻塞通道时,所有其他goroutine必须排队等候。当通道被解锁时,计数已经被接收并从通道中移除,因此下一个goroutine将获得下一个计数值。

答案 2 :(得分:6)

这很复杂。

另外,看看GOMAXPROCS = NumCPU+1会发生什么。例如,

package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU() + 1)
    fmt.Print(runtime.GOMAXPROCS(0))
    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)
}

输出:

5, original, hi from 0, hi from 4

并且,看看缓冲频道会发生什么。例如,

package main

import "fmt"

func main() {
    c := make(chan string, 5+1)
    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

你也应该能够解释这些案例。

答案 3 :(得分:4)

我研究过现有的解决方案并创建了简单的广播库https://github.com/grafov/bcast

    group := bcast.NewGroup() // you created the broadcast group
    go bcast.Broadcasting(0) // the group accepts messages and broadcast it to all members

    member := group.Join() // then you join member(s) from other goroutine(s)
    member.Send("test message") // or send messages of any type to the group 

    member1 := group.Join() // then you join member(s) from other goroutine(s)
    val := member1.Recv() // and for example listen for messages

答案 4 :(得分:0)

对于多个goroutine在一个频道上收听,是的,这是可能的。关键点是消息本身,你可以定义一些这样的消息:

package main

import (
    "fmt"
    "sync"
)

type obj struct {
    msg string
    receiver int
}

func main() {
    ch := make(chan *obj) // both block or non-block are ok
    var wg sync.WaitGroup
    receiver := 25 // specify receiver count

    sender := func() {
        o := &obj {
            msg: "hello everyone!",
            receiver: receiver,
        }
        ch <- o
    }
    recv := func(idx int) {
        defer wg.Done()
        o := <-ch
        fmt.Printf("%d received at %d\n", idx, o.receiver)
        o.receiver--
        if o.receiver > 0 {
            ch <- o // forward to others
        } else {
            fmt.Printf("last receiver: %d\n", idx)
        }
    }

    go sender()
    for i:=0; i<reciever; i++ {
        wg.Add(1)
        go recv(i)
    }

    wg.Wait()
}

输出是随机的:

5 received at 25
24 received at 24
6 received at 23
7 received at 22
8 received at 21
9 received at 20
10 received at 19
11 received at 18
12 received at 17
13 received at 16
14 received at 15
15 received at 14
16 received at 13
17 received at 12
18 received at 11
19 received at 10
20 received at 9
21 received at 8
22 received at 7
23 received at 6
2 received at 5
0 received at 4
1 received at 3
3 received at 2
4 received at 1
last receiver 4