2016-03-17 12 views
9

Sto lavorando su una libreria concorrente Go, e sono incappato in due modelli distinti di sincronizzazione tra goroutines i cui risultati sono simili:Qual è il vantaggio di sync.WaitGroup su Canali?

Utilizzando Waitgroup

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() 
} 

utilizzando il canale

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 
} 

Sono stato avvisato che sync.WaitGroup è leggermente più performante, e io h avendolo visto comunemente. Tuttavia, trovo i canali più idiomatici. Qual è il vero vantaggio dell'uso di sync.WaitGroup sui canali e/o quale potrebbe essere la situazione quando è meglio?

+1

Nel secondo esempio, la sincronizzazione è errata. blocchi fino a quando la prima goroutine non manda sul canale, non fino all'ultimo. –

+2

Dai un'occhiata a: https://github.com/golang/go/wiki/MutexOrChannel#wait-group – molivier

+0

@Not_a_Golfer per qualche motivo quando ho cambiato l'argomento nella funzione della goroutine su 'word' stampa tutti i membri correttamente. – PieOhPah

risposta

17

Indipendentemente dalla correttezza del tuo secondo esempio (come spiegato nei commenti, non stai facendo quello che pensi, ma è facilmente risolvibile), tendo a pensare che il primo esempio sia più facile da capire.

Ora, non direi nemmeno che i canali sono più idiomatici. I canali essendo una caratteristica della lingua Go non dovrebbero significare che sia idiomatico usarli quando possibile. Ciò che è idiomatico in Go è utilizzare la soluzione più semplice e più semplice da comprendere: in questo caso, lo WaitGroup trasmette sia il significato (la tua funzione principale è Wait per i lavoratori) sia il meccanico (i lavoratori notificano quando sono Done).

meno che non sei in un caso molto specifico, non recommand usando la soluzione del canale qui ...

2

Se siete particolarmente appiccicoso sull'utilizzo solo i canali, quindi ha bisogno di essere fatto in modo diverso (se usiamo il tuo esempio fa, come sottolinea @Not_a_Golfer, produrrà risultati errati).

Un modo è rendere un canale di tipo int. Nel processo di lavoro invia un numero ogni volta che completa il lavoro (questo può essere anche l'id del lavoro univoco, se vuoi puoi tenerlo nel ricevitore).

Nella routine di ricezione principale del ricevitore (che conoscerà il numero esatto di lavori inoltrati): eseguire un ciclo di intervalli su un canale, contare fino a quando il numero di lavori inoltrati non viene eseguito e interrompere il ciclo quando tutti i lavori sono completati Questo è un buon modo se vuoi tenere traccia di ognuno dei lavori completati (e magari fare qualcosa se necessario).

Ecco il codice per il vostro riferimento. Il decremento di totalJobsLeft sarà sicuro poiché verrà eseguito solo nel ciclo di copertura del canale!

//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) 

} 
Problemi correlati