2016-03-17 9 views
9

pracuję nad jednoczesnym biblioteki Go, i natknąłem się na dwóch różnych wzorcach synchronizacji między goroutines których wyniki są podobne:Jaka jest przewaga synchronizacji.WaitGroup nad kanałami?

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

Korzystanie kanał

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 
} 

Zostałem poinformowany, że sync.WaitGroup jest nieco bardziej wydajne, a ja h ave widziałem, że jest powszechnie używany. Jednak uważam, że kanały są bardziej idiomatyczne. Jaka jest prawdziwa zaleta korzystania z kanałów i/lub jaka może być sytuacja, gdy jest lepiej?

+1

W drugim przykładzie synchronizacja jest nieprawidłowa. blokujesz, dopóki pierwszy goroutine nie wyśle ​​na kanał, aż do ostatniego. –

+2

Spójrz na: https://github.com/golang/go/wiki/MutexOrChannel#wait-group – molivier

+0

@Not_a_Golfer z jakiegoś powodu, kiedy zmieniłem argument w funkcji goroutine na 'word' wypisuje wszystkich członków prawidłowo. – PieOhPah

Odpowiedz

17

Niezależnie od poprawności drugiego przykładu (jak wyjaśniono w komentarzach, nie robisz tego, co myślisz, ale można je łatwo naprawić), mam tendencję do myślenia, że ​​pierwszy przykład jest łatwiejszy do uchwycenia.

Teraz nie powiedziałbym nawet, że kanały są bardziej idiomatyczne. Kanały będące cechą podpisu w języku Go nie powinny oznaczać, że używanie ich jest idiotyczne, gdy tylko jest to możliwe. To, co jest idiomatyczne w Go, to użycie najprostszego i najłatwiejszego do zrozumienia rozwiązania: tutaj, WaitGroup przekazuje zarówno znaczenie (twoja główna funkcja to Wait dla pracowników do zrobienia), jak i mechanik (pracownicy powiadamiają, kiedy są Done).

Chyba że jesteś w bardzo konkretnym przypadku, nie polecam korzystania z rozwiązania Channel tutaj ...

2

Jeśli jesteś szczególnie lepki o użyciu tylko kanały, to trzeba zrobić inaczej (jeśli używamy Twój przykład robi, jak zaznacza @Not_a_Golfer, da niepoprawne wyniki).

Jednym ze sposobów jest utworzenie kanału typu int. W procesie roboczym wyślij numer za każdym razem, gdy zakończy pracę (może to być również unikalny identyfikator zadania, jeśli chcesz, możesz śledzić to w odbiorniku).

W głównej rutyrze odbiornika (która będzie znała dokładną liczbę złożonych zadań) - wykonaj pętlę zasięgu na kanale, licz na do momentu, gdy liczba przesłanych zleceń nie zostanie wykonana, i przerwij pętlę, gdy wszystkie zadania są zakończone. Jest to dobry sposób, jeśli chcesz śledzić każde z zadań (a może w razie potrzeby coś zrobić).

Oto kod w celach informacyjnych. Zmniejszanie totalJobsLeft będzie bezpieczne, ponieważ kiedykolwiek zostanie zrobione tylko w pętli zasięgu kanału!

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

} 
Powiązane problemy