我收到的以下go代码出了什么问题'所有goroutines都睡着了 - 死锁!'

时间:2011-11-09 06:25:19

标签: go channel goroutine

我正在尝试实现此处建议的观察者模式; Observer pattern in Go language

(上面列出的代码不编译且不完整)。这里是一个完整的代码编译但是我遇到了死锁错误。

package main

import (
    "fmt"
)

type Publisher struct{
    listeners []chan int
}

type Subscriber struct{
    Channel chan int
    Name string
}

func (p *Publisher) Sub(c chan int){
    p.listeners = append(p.listeners, c)
}

func (p *Publisher) Pub(m int, quit chan int){
    for _, c := range p.listeners{
        c <- m
    }
    quit <- 0
}

func (s *Subscriber) ListenOnChannel(){
    data := <-s.Channel
    fmt.Printf("Name: %v; Data: %v\n", s.Name, data)            
}

func main() {
    quit := make(chan int)
    p := &Publisher{}
    subscribers := []*Subscriber{&Subscriber{Channel: make(chan int), Name: "1"}, &Subscriber{Channel: make(chan int), Name: "2"}, &Subscriber{Channel: make(chan int), Name: "3"}}
    for _, v := range subscribers{
        p.Sub(v.Channel)
        go v.ListenOnChannel() 
    }

    p.Pub(2, quit)

    <-quit              
}

另外,如果我完全摆脱'退出',我没有错误,但它只打印第一条记录。

2 个答案:

答案 0 :(得分:5)

问题是你要从quit收到的同一个goroutine上退出。

quit的缓冲区大小为0,这意味着为了继续,必须在一侧有一个发送方,而另一侧的接收方 。你正在发送,但没有人在另一端,所以你永远等待。在这种特殊情况下,Go运行时能够检测到问题和恐慌。

当您删除quit时仅打印第一个值的原因是您的主要goroutine正在退出,然后剩下的两个才能打印。

只是增加通道缓冲区大小来摆脱这样的问题。它可以提供帮助(虽然在这种情况下它没有),但它只能解决问题,并没有真正解决根本原因。增加通道的缓冲区大小绝对是一种优化。实际上,通常在没有缓冲区的情况下开发更好,因为它会使并发问题更加明显。

有两种方法可以解决问题:

  • 保留quit,但在ListenOnChannel内的每个goroutine中发送0。在main中,请确保在继续之前从每个goroutine收到一个值。 (在这种情况下,您将等待三个值。)
  • 使用WaitGroup。在文档中有一个很好的例子。

答案 1 :(得分:2)

总的来说,这看起来不错,但有一个问题。请记住,通道是缓冲的或非缓冲的(同步或异步)。当您将未缓冲的通道发送到具有完整缓冲区的通道时,发送方将阻塞,直到接收方已从通道中删除数据为止。

因此,我会问一两个问题:

  1. 戒烟渠道是同步还是异步?
  2. 当执行命中quit<-0时,Pub中会发生什么?
  3. 解决问题并允许代码运行的一种解决方案是将倒数第二个代码行更改为go p.Pub(2, quit)。但还有另一种解决方案。你能看出它是什么吗?

    如果从原始代码中删除<-quit,我实际上并没有采取相同的行为。这不应该影响输出,因为写入该行永远不会执行。