What's wrong with the following go code that I receive 'all goroutines are asleep – deadlock!'

huangapple go评论100阅读模式
英文:

What's wrong with the following go code that I receive 'all goroutines are asleep - deadlock!'

问题

我正在尝试实现一个在这里建议的观察者模式;https://stackoverflow.com/questions/3733761/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              
}

另外,如果我完全去掉 'quit',就不会出现错误,但只会打印第一条记录。

英文:

I'm trying to implement an Observer Pattern suggested here; https://stackoverflow.com/questions/3733761/observer-pattern-in-go-language

(the code listed above doesn't compile and is incomplete). Here, is a complete code that compiles but I get deadlock error.

package main

import (
    &quot;fmt&quot;
)

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 &lt;- m
    }
    quit &lt;- 0
}

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

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

    p.Pub(2, quit)

    &lt;-quit              
}

Also, if I get rid of 'quit' completely, I get no error but it only prints first record.

答案1

得分: 5

问题是你在同一个goroutine中发送退出信号,而这个goroutine又在接收quit信号。

quit的缓冲区大小为0,这意味着为了继续执行,必须同时有一个发送者和一个接收者。你在发送,但没有人在另一端接收,所以你会一直等待。在这种特殊情况下,Go运行时能够检测到问题并引发panic。

当你移除quit时,只有第一个值被打印的原因是你的主goroutine在剩下的两个goroutine能够打印之前退出了。

不要仅仅增加通道缓冲区大小来解决这类问题。这可能有所帮助(尽管在这种情况下并没有),但它只是掩盖了问题,并没有真正修复潜在的原因。增加通道的缓冲区大小严格来说是一种优化。实际上,通常最好不要使用缓冲区,因为它能更明显地暴露并发问题。

修复问题有两种方法:

  • 保留quit,但在ListenOnChannel中的每个goroutine中向其发送0。在main中,在继续之前确保从每个goroutine接收一个值。(在这种情况下,你将等待三个值。)

  • 使用WaitGroup。文档中有一个很好的示例说明它的工作原理。

英文:

The problem is that you're sending to quit on the same goroutine that's receiving from quit.

quit has a buffer size of 0, which means that in order to proceed there has to be a sender on one side and a receiver on the other at the same time. You're sending, but no one's on the other end, so you wait forever. In this particular case the Go runtime is able to detect the problem and panic.

The reason only the first value is printed when you remove quit is that your main goroutine is exiting before your remaining two are able to print.

Do not just increase channel buffer sizes to get rid of problems like this. It can help (although in this case it doesn't), but it only covers up the problem and doesn't truly fix the underlying cause. Increasing a channel's buffer size is strictly an optimization. In fact, it's usually better to develop with no buffer because it makes concurrency problems more obvious.

There are two ways to fix the problem:

  • Keep quit, but send 0 on it in each goroutine inside ListenOnChannel. In main, make sure you receive a value from each goroutine before moving on. (In this case, you'll wait for three values.)

  • Use a WaitGroup. There's a good example of how it works in the documentation.

答案2

得分: 2

总体上看起来不错,但有一个问题。请记住,通道可以是有缓冲的或无缓冲的(同步或异步)。当您发送到一个无缓冲的通道或一个已满缓冲区的通道时,发送方将被阻塞,直到接收方从通道中取出数据。

所以,我会问一两个问题:

  1. 退出通道是同步还是异步的?
  2. 当执行到quit<-0时,Pub函数会发生什么?

解决您的问题并使代码运行的一个解决方案是将倒数第二行代码改为go p.Pub(2, quit)。但还有另一个解决方案。你能看到是什么吗?

如果我从原始代码中删除<-quit,我实际上并没有得到与您相同的行为。而且这不应该影响输出,因为按照现有的写法,该行永远不会被执行。

英文:

In general this looks good, but there is one problem. Remember that channels are either buffered or unbuffered (synchronous or asynchronous). When you send to an unbuffered channel or to a channel with a full buffer the sender will block until the data has been removed from the channel by a receiver.

So with that, I'll ask a question or two of my own:

  1. Is the quit channel synchronous or asynchronous?
  2. What happens in Pub when execution hits quit&lt;-0?

One solution that fixes your problem and allows the code to run is to change the second-to-last code line to be go p.Pub(2, quit). But there is another solution. Can you see what it is?

I don't actually get the same behavior you do if I remove &lt;-quit from the original code. And this should not affect the output because as it is written that line is never executed.

huangapple
  • 本文由 发表于 2011年11月9日 14:25:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/8061170.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定