2016-03-17 9 views
9

Ich arbeite an einer gleichzeitigen Go-Bibliothek, und ich stolperte über zwei verschiedene Muster der Synchronisation zwischen goroutines deren Ergebnisse sind ähnlich:Was ist der Vorteil von sync.WaitGroup über Kanäle?

Waitgroup Mit

var wg sync.WaitGroup 
func main() { 
     words := []string{ "foo", "bar", "baz" } 

     for _, word := range words { 
       wg.Add(1) 
       go func(word string) { 
         time.Sleep(1 * time.Second) 
         defer wg.Done() 
         fmt.Println(word) 
       }(word) 
     } 
     // do concurrent things here 

     // blocks/waits for waitgroup 
     wg.Wait() 
} 

Kanal Mit

func main() { 
     words = []string{ "foo", "bar", "baz" } 
     done := make(chan bool) 
     defer close(done) 
     for _, word := range words { 
       go func(word string) { 
         time.Sleep(1 * time.Second) 
         fmt.Println(word) 
         done <- true 
       }(word) 
     } 

     // Do concurrent things here 

     // This blocks and waits for signal from channel 
     <-done 
} 

ich wurde darauf hingewiesen, dass sync.WaitGroup etwas mehr performant ist, und ich h Ich habe gesehen, dass es häufig verwendet wird. Allerdings finde ich Kanäle idiomatischer. Was ist der wirkliche Vorteil der Verwendung von sync.WaitGroup über Kanäle und/oder was könnte die Situation sein, wenn es besser ist?

+1

In Ihrem zweiten Beispiel ist die Synchronisierung falsch. du blockierst, bis die erste goroutine auf dem Kanal sendet, nicht bis zum letzten. –

+2

Werfen Sie einen Blick auf: https://github.com/golang/go/wiki/MutexOrChannel#wait-group – molivier

+0

@Not_a_Golfer aus irgendeinem Grund, wenn ich das Argument in der Goroutine-Funktion zu "Wort" geändert hat, druckt es alle Mitglieder aus korrekt. – PieOhPah

Antwort

17

Unabhängig von der Korrektheit Ihres zweiten Beispiels (wie in den Kommentaren erläutert, tun Sie nicht, was Sie denken, aber es ist leicht zu reparieren), denke ich, dass das erste Beispiel leichter zu begreifen ist.

Nun, ich würde nicht einmal sagen, dass die Kanäle mehr idiomatische sind. Kanäle, die ein Unterscheidungsmerkmal der Go-Sprache sind, sollten nicht bedeuten, dass es idiomatisch ist, sie wann immer möglich zu verwenden. Was idiomatisch in Go ist, ist die einfachste und am einfachsten zu verstehende Lösung: hier die WaitGroup sowohl die Bedeutung (Ihre Hauptfunktion ist Wait ing für Arbeiter zu tun) und die Mechanik (die Arbeiter benachrichtigen, wenn sie Done sind).

Sofern Sie in einem ganz bestimmten Fall sind, ich empfehlen hier nicht die Kanallösung ...

2

Wenn Sie besonders klebrig sind etwa nur die Kanäle verwenden, dann muss es anders gemacht werden (wenn wir verwenden Ihr Beispiel tut dies, da @Not_a_Golfer darauf hinweist, dass es falsche Ergebnisse erzeugt).

Eine Möglichkeit besteht darin, einen Kanal vom Typ int zu erstellen. Im Worker-Prozess senden Sie jedes Mal eine Nummer, wenn der Job abgeschlossen ist (dies kann auch die eindeutige Job-ID sein, wenn Sie möchten, dass Sie dies im Empfänger verfolgen können).

In der Receiver-Main-Go-Routine (die die genaue Anzahl der eingereichten Jobs kennt) - eine Bereichsschleife über einen Kanal ausführen, solange zählen, bis die Anzahl der übergebenen Jobs nicht erreicht ist, und alle ausbrechen Jobs sind abgeschlossen. Dies ist ein guter Weg, wenn Sie jeden abgeschlossenen Job verfolgen möchten (und eventuell etwas tun, wenn nötig).

Hier ist der Code für Ihre Referenz. Decrementieren von totalJobsLeft wird sicher sein, da es nur in der Range-Schleife des Kanals durchgeführt wird!

//This is just an illustration of how to sync completion of multiple jobs using a channel 
//A better way many a times might be to use wait groups 

package main 

import (
    "fmt" 
    "math/rand" 
    "time" 
) 

func main() { 

    comChannel := make(chan int) 
    words := []string{"foo", "bar", "baz"} 

    totalJobsLeft := len(words) 

    //We know how many jobs are being sent 

    for j, word := range words { 
     jobId := j + 1 
     go func(word string, jobId int) { 

      fmt.Println("Job ID:", jobId, "Word:", word) 
      //Do some work here, maybe call functions that you need 
      //For emulating this - Sleep for a random time upto 5 seconds 
      randInt := rand.Intn(5) 
      //fmt.Println("Got random number", randInt) 
      time.Sleep(time.Duration(randInt) * time.Second) 
      comChannel <- jobId 
     }(word, jobId) 
    } 

    for j := range comChannel { 
     fmt.Println("Got job ID", j) 
     totalJobsLeft-- 
     fmt.Println("Total jobs left", totalJobsLeft) 
     if totalJobsLeft == 0 { 
      break 
     } 
    } 
    fmt.Println("Closing communication channel. All jobs completed!") 
    close(comChannel) 

} 
Verwandte Themen