2016-05-13 7 views
4

Hier ist mein Programm, das Deadlock produziert, wie vermeide ich es und was ist das empfohlene Muster, um mit dieser Art von Situation umzugehen.Wie Deadlock in diesem Golang-Programm zu vermeiden?

Das Problem ist nach Timeout, wie kann ich feststellen, dass es keinen Leser auf meinem Kanal gibt?

var wg sync.WaitGroup 

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
    select { 
    case x := <-c: 
     fmt.Println("Read", x) 
    case <-ti: 
     fmt.Println("TIMED OUT") 
    } 
    wg.Done() 
} 

Antwort

1

Sie haben einen ungepufferten Kanal. Gemäß der docs:

Wenn der Kanal ungepuffert ist, empfangen die Sender blockiert, bis der Empfänger hat den Wert . Wenn der Kanal einen Puffer hat, hat die Senderblöcke nur, bis der Wert in den Puffer

den Kanal gepuffert werden kopiert Durch Ändern, können wir Deadlock vermeiden.

c := make(chan int, 10) // holds 10 ints 

Ich schlage vor, auch https://golang.org/doc/effective_go.html#channels lesen, ist es ein paar gute Sachen drin zu den Kanälen im Zusammenhang stand.

6

Also schauen wir uns an, was wirklich in Ihrer Quelle passiert. Sie haben zwei goroutines (es gibt mehr als zwei, aber wir werden auf die expliziten konzentrieren), main und readFromChannel.

Schauen wir uns an, was readFromChannel tut:

if channel `c` is not empty before `ti` has expired, print its contents and return, after signalling its completion to wait group. 
if `ti` has expired before `c` is not empty, print "TIMED OUT" and return, after signalling its completion to wait group. 

jetzt Main:

adds to waitgroup 
make a channel `c` 
start a goroutine `readFromChannel` 
sleep for 5 seconds 
send 10 to channel `c` 
call wait for waitgroup 

Jetzt lässt, für den Code der Ablauf der Ausführung durchlaufen, gleichzeitig (Code kann/ausführen kann nicht in dieser Reihenfolge jedes Mal, bedenken Sie das)

1) wg.Add(1) 
2) c := make(chan int) 
3) go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
#timer ti starts# 
4) time.Sleep(time.Duration(5) * time.Second) 
#MAIN Goroutine begins sleep 
#timer ti expires# 
5) case <-ti: 
6) fmt.Println("TIMED OUT") 
7) wg.Done() 
# readFromChannel Goroutine returns # 
#MAIN Goroutine exits sleep# 
8) c<-10 
9) ......#DEADLOCK# 

Jetzt können Sie gue Warum hast du eine Sackgasse? In nicht blockierten Kanälen blockiert, bis etwas am anderen Ende des Kanals passiert, unabhängig davon, ob Sie senden oder empfangen. So c <- 10 wird blockieren, bis etwas vom anderen Ende von c liest, aber die Goroutine, die Sie dafür hatten, ist aus dem Bild vor 2 Sekunden herausgefallen. Daher c blockiert für immer, und seit main ist die letzte goroutine links, erhalten Sie einen Deadlock.

Wie kann ich das verhindern? Stellen Sie bei der Verwendung von Kanälen sicher, dass sich am anderen Ende des Kanals immer ein receive für jede send befindet. Sie können auch einen gepufferten Kanal verwenden, aber in Ihrem obigen Code wäre es nicht die "richtige" Lösung.

Hier ist meine Lösung für das Deadlock:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    wg.Wait() 
} 

func readFromChannel(c chan int, ti <-chan time.Time) { 
     // the forloop will run forever 
    loop: // ** 
    for { 
     select { 
      case x := <-c: 
        fmt.Println("Read", x) 
        break loop // breaks out of the for loop and the select ** 
      case <-ti: 
        fmt.Println("TIMED OUT") 
      } 
    } 
    wg.Done() 
} 

** see this answer for details

+0

Dies ist eines der nützlichsten Antworten, die ich in einiger Zeit gelesen habe. Nur um sicherzugehen, dass ich folge: das "Fix" funktioniert, weil es den Empfängerkanal auch nach einer Auszeit hält. Also 'wg.Done()' (und das 'main' go routine beenden) wird immer nur passieren, wenn etwas von' c' eingelesen wurde, oder? –

+1

Richtig, aber um etwas aufzuräumen, hält es die ** goroutine ** am Laufen. – AJPennster

0

Ihr Problem ist, dass Sie select Anweisung verwenden, aber sie sind nicht in einem goroutine verwenden.

go func() { 
    for { 
     select { 
     case x := <-c: 
      fmt.Println("Read", x) 
     case <-ti: 
      fmt.Println("TIMED OUT") 
     } 
    } 
}() 

die Werte aus verschiedenen gleichzeitig ausgeführten goroutines bekommen kann mit dem Auswahl Schlüsselwort erreicht werden, die eng an die Anweisung Schaltersteuerung ähnelt und ist manchmal auch die Kommunikation wechseln.

Die Verwendung einer Sendeoperation in einer Select-Anweisung mit einem Standardfall garantiert, dass der Sendevorgang nicht blockiert wird! Wenn es keine Fälle gibt, blockiert die Auswahl die Ausführung für immer.

https://play.golang.org/p/Ai1ggveb4s

0

Dies ist eine ältere Frage, aber ich bin Tauchen mir tief in Lernkanäle und fand hier das.

Ich denke, Sie müssen nur den Kanal schließen, nachdem Sie es gesendet haben?

Code:

func main() { 
    wg.Add(1) 
    c := make(chan int) 
    go readFromChannel(c, time.After(time.Duration(2)*time.Second)) 
    time.Sleep(time.Duration(5) * time.Second) 
    c <- 10 
    close(c) // <- CLOSE IT HERE 
    wg.Wait() 
}