2016-04-04 8 views
5

der Verwendung habe ich einige Probleme mit dem folgenden Code:Der beste Weg sync.WaitGroup mit externer Funktion

package main 

import (
"fmt" 
"sync" 
) 
// This program should go to 11, but sometimes it only prints 1 to 10. 
func main() { 
    ch := make(chan int) 
    var wg sync.WaitGroup 
    wg.Add(2) 
    go Print(ch, wg) // 
    go func(){ 

     for i := 1; i <= 11; i++ { 
      ch <- i 
     } 

     close(ch) 
     defer wg.Done() 


    }() 

    wg.Wait() //deadlock here 
} 

// Print prints all numbers sent on the channel. 
// The function returns when the channel is closed. 
func Print(ch <-chan int, wg sync.WaitGroup) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    } 
    defer wg.Done() 
} 

ich einen toten Punkt an der angegebenen Stelle zu bekommen. Ich habe versucht, wg.Add(1) anstelle von 2 und es löst mein Problem. Meine Überzeugung ist, dass ich den Kanal nicht erfolgreich als Argument an die Printer Funktion senden kann. Gibt es eine Möglichkeit, das zu tun? Andernfalls wird eine Lösung für mein Problem Ersetzen der go Print(ch, wg) Linie mit:

go func() { 
Print(ch) 
defer wg.Done() 
} 

und Ändern der Printer Funktion:

func Print(ch <-chan int) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    } 

} 

Was ist die beste Lösung?

Antwort

10

Nun, zunächst Ihr tatsächlicher Fehler ist, dass Sie die Print Methode eine Kopie der sync.WaitGroup geben, so dass es nicht aufgerufen die Done() Methode auf die, die Sie Wait() ing auf sind.

Versuchen Sie stattdessen:

package main 

import (
    "fmt" 
    "sync" 
) 

func main() {  
    ch := make(chan int) 

    var wg sync.WaitGroup 
    wg.Add(2)  

    go Print(ch, &wg) 

    go func() { 
     for i := 1; i <= 11; i++ { 
      ch <- i 
     } 
     close(ch) 
     defer wg.Done() 
    }()   

    wg.Wait() //deadlock here 
}     

func Print(ch <-chan int, wg *sync.WaitGroup) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    }    
    defer wg.Done() 
} 

nun Ihre Print Methode ändert die WaitGroup davon zu entfernen, ist eine allgemein gute Idee: Die Methode braucht nicht etwas zu wissen wartet, damit er seine Arbeit beenden .

+1

Verstanden, ich wusste nicht, dass Sie die Adresse erforderlich zu sein gesendet an 'Print' anstelle der' WaitGroup' selbst. Ich stimme zu, dass die Methode nicht über die 'WaitGroup' wissen muss, aber nehme an, dass ich das trotzdem möchte. Was macht der Stern dann? Wählt es die ACTUAL 'WaitGrooup' aus, die ich in' main' definiert habe? Und warum ist das nicht eine Kopie? – Sahand

+0

Die '* sync.WaitGroup' teilt dem Compiler mit, dass Sie einen Zeiger auf eine' WaitGroup' anstelle einer 'WaitGroup' wünschen. Der Compiler erwartet also, dass Sie die Methode aufrufen, indem Sie ihr eine Adresse geben, also das '& wg'. – Elwinar

+2

Denken Sie nicht, dass Sie die Warteliste von print löschen können, da mains wg.Wait durchfallen kann, bevor der letzte Wert gedruckt wird, wenn Sie nur auf den Abschluss des Absenders gewartet haben. Auch kein Grund, eine Funktion mit einer Verschiebung zu beenden wg.Done(), im Grunde bedeutet, bedeutet, führen Sie dies am Ende. Drop the defer –

1

Ich stimme mit @ Elwinar Lösung, dass das Hauptproblem in Ihrem Code durch die Weitergabe einer Kopie Ihrer Waitgroup an die Print Funktion verursacht wird.

Das bedeutet, dass wg.Done() auf einer Kopie von wg betrieben wird, die Sie in der main definiert haben. Daher wg in der main konnte nicht verringert werden, und damit ein Deadlock passiert, wenn Sie wg.Wait() in Haupt.

Da Sie auch über die beste Praxis gefragt werden, könnte ich Ihnen einige Anregungen meiner eigenen:

  • nicht defer wg.Done() in Print entfernen Sie. Da Ihre goroutine in main ein Sender ist und print ein Empfänger ist, verursacht das Entfernen von wg.Done() in der Empfängerroutine einen unvollendeten Empfänger. Dies liegt daran, dass nur Ihr Sender mit Ihrem Hauptgerät synchronisiert wird. Nachdem Ihr Absender fertig ist, ist Ihr Hauptgerät fertig, aber es ist möglich, dass der Empfänger noch funktioniert. Mein Punkt ist: lassen Sie nicht einige baumelnde goroutines herum, nachdem Ihre Hauptroutine beendet ist. Schließ sie oder warte auf sie.

  • Denken Sie daran, Panik-Recovery überall zu tun, vor allem anonyme Goroutine. Ich habe viele Golang-Programmierer gesehen, die vergessen haben, Panik-Erholung in Goroutines zu setzen, auch wenn sie daran denken, sich in normalen Funktionen zu erholen. Es ist von entscheidender Bedeutung, wenn Sie möchten, dass sich Ihr Code korrekt oder zumindest elegant verhält, wenn etwas Unerwartetes passiert.

  • Verwenden defer vor jeder kritischen Anrufe, wie sync bezogene Anrufe, am Anfang da Sie nicht wissen, wo der Code brechen könnte. Nehmen wir an, Sie haben defer vor wg.Done() entfernt, und in Ihrem Beispiel tritt eine Panik in Ihrer anonymen Goroutine auf.Wenn Sie keine Panik haben, wird es in Panik geraten. Aber was passiert, wenn Sie sich in Panik erholen? Jetzt ist alles in Ordnung? Nein. Sie werden bei wg.Wait() Deadlock bekommen, da Ihre wg.Done() wegen Panik übersprungen wird! Bei Verwendung von defer wird diese wg.Done() jedoch am Ende ausgeführt, selbst wenn eine Panik aufgetreten ist. Auch das Zurückstellen vor close ist wichtig, da dessen Ergebnis auch die Kommunikation beeinflusst.

So, hier ist der Code nach den Punkten geändert ich oben erwähnt:

package main 

import (
    "fmt" 
    "sync" 
) 

func main() { 
    ch := make(chan int) 
    var wg sync.WaitGroup 
    wg.Add(2) 
    go Print(ch, &wg) 
    go func() { 

     defer func() { 
      if r := recover(); r != nil { 
       println("panic:" + r.(string)) 
      } 
     }() 

     defer func() { 
      wg.Done() 
     }() 

     for i := 1; i <= 11; i++ { 
      ch <- i 

      if i == 7 { 
       panic("ahaha") 
      } 
     } 

     println("sender done") 
     close(ch) 
    }() 

    wg.Wait() 
} 

func Print(ch <-chan int, wg *sync.WaitGroup) { 
    defer func() { 
     if r := recover(); r != nil { 
      println("panic:" + r.(string)) 
     } 
    }() 

    defer wg.Done() 

    for n := range ch { 
     fmt.Println(n) 
    } 
    println("print done") 
} 

Hoffe, es hilft :)

+0

Vielen Dank, Alter! – Sahand

+0

Ich stimme überhaupt nicht mit dem _Don't das wg von Print_ entfernen. Aber vielleicht wurde mein Vorschlag nicht richtig verstanden, was ich rate ist, so etwas zu tun: http://play.golang.org/p/lOopY0bYHT Auf diese Weise weiß Ihre Routine nicht, dass ist synchronisiert, was macht es einfacher. Als Faustregel gilt: * Wenn die Synchronisierung nicht Teil des Algorithmus ist, sollte Ihr Code dies niemals bemerken *. Das heißt, eine Methode, die etwas auf den Bildschirm schreibt, sollte niemals eine Synchronisationsbewußtsein brauchen. – Elwinar

+0

Es ist eine gute Idee, sich von Paniken zu erholen, aber in diesem Beispiel ist es übertrieben. Das einzige, was in Panik geraten kann, ist das Senden an einen geschlossenen Kanal, und Sie sind derjenige, der es schließt. Fallen Sie nicht in die Falle, jedes Mal alles zu überprüfen, das wird Ihren Code mit unnötigen Überprüfungen aufblähen und das Lesen erschweren. – Elwinar