广播员:所有的goroutines都睡着了 - 僵局

时间:2014-05-28 22:06:44

标签: go

下面的代码(http://play.golang.org/p/ikUtdoKOo5)应该向许多客户端广播消息。但它不起作用,我无法弄清楚原因。

package main

import "fmt"

type Broadcaster struct {
    Clients []Client
}

func (b *Broadcaster) Broadcast(msg string) {
    for _, c := range b.Clients {
        go func() {
            c.Inbox() <- msg
        }()
    }
}

type Client interface {
    Inbox() chan string
}

type TestClient struct {
    Messages chan string
}

func (tc TestClient) Inbox() chan string {
    return tc.Messages
}

func main() {
    client1 := TestClient{Messages: make(chan string)}
    client2 := TestClient{Messages: make(chan string)}
    broadcaster := Broadcaster{Clients: []Client{client1, client2}}

    broadcaster.Broadcast("sos")

    fmt.Printf("client1: '%s'\n", <-client1.Messages)
    fmt.Printf("client2: '%s'\n", <-client2.Messages)
}


错误:

go run main.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /Users/artem/projects/gocode/src/github.com/artemave/broadcaster/main.go:36 +0x1f3

goroutine 3 [chan send]:
main.func·001()
        /Users/artem/projects/gocode/src/github.com/artemave/broadcaster/main.go:12 +0x5f
created by main.(*Broadcaster).Broadcast
        /Users/artem/projects/gocode/src/github.com/artemave/broadcaster/main.go:13 +0xcd

goroutine 4 [chan send]:
main.func·001()
        /Users/artem/projects/gocode/src/github.com/artemave/broadcaster/main.go:12 +0x5f
created by main.(*Broadcaster).Broadcast
        /Users/artem/projects/gocode/src/github.com/artemave/broadcaster/main.go:13 +0xcd


更新:

go vet工具揭示了问题:

% go vet
main.go:12: range variable c enclosed by function

2 个答案:

答案 0 :(得分:4)

您的问题是在闭包中使用变量c。 由于c是单个变量,因此在执行go例程时,c很可能会设置为client2,并且两条消息都将被广播到该客户端,并且没有到client1

使用函数参数修复它的一种方法:

func (b *Broadcaster) Broadcast(msg string) {
    for _, c := range b.Clients {       
        go func(c Client) {
            c.Inbox() <- msg                
        }(c)
    }
}

答案 1 :(得分:4)

这是在for-range循环中重新分配c所带来的一个微妙的错误。它看起来有点奇怪,但你可以在std库中看到这个模式:

func (b *Broadcaster) Broadcast(msg string) {
    for _, c := range b.Clients {
        c := c  // redeclare c for the closure
        go func() {
            c.Inbox() <- msg
        }()
    }
}

http://golang.org/doc/faq#closures_and_goroutines