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

StackOverflow https://stackoverflow.com/questions/8061170

  •  24-02-2021
  •  | 
  •  

Вопрос

I'm trying to implement an Observer Pattern suggested here; 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 (
    "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              
}

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

Это было полезно?

Решение

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.

Другие советы

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<-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 <-quit from the original code. And this should not affect the output because as it is written that line is never executed.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top