2016-04-04 24 views
5

Mam pewne problemy z następującego kodu:Najlepszy sposób korzystania z funkcji sync.WaitGroup zewnętrznego

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

dostaję impasu w określonym miejscu. Próbowałem ustawić wg.Add(1) zamiast 2 i rozwiązuje mój problem. Jestem przekonany, że nie udało mi się wysłać kanału jako argumentu do funkcji Printer. Czy jest jakiś sposób na zrobienie tego? W przeciwnym razie rozwiązanie mojego problemu jest zastąpienie linii go Print(ch, wg) z:

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

i zmiany funkcji Printer do:

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

} 

Co jest najlepszym rozwiązaniem?

Odpowiedz

10

Cóż, pierwszy rzeczywisty błąd jest to, że dajesz metoda Print kopię sync.WaitGroup, więc nie wywołać metodę z jednej Done() jesteś Wait() ing dalej.

Spróbuj to zamiast:

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

Teraz, zmieniając metodę Print do usunięcia WaitGroup niego jest ogólnie dobry pomysł: metoda nie musi wiedzieć coś czeka na to, aby zakończyć swoją pracę .

+1

Got to, ja nie wiem, co potrzebne, aby być adres wysłane do 'Print' zamiast do samej' WaitGroup'. Zgadzam się, że metoda nie musi wiedzieć o 'WaitGroup', ale przypuśćmy, że i tak chcę. Co zatem robi gwiazda? Czy wybiera on RZECZYWISTY 'WaitGrooup', który zdefiniowałem w' main'? I dlaczego ten nie jest kopią? – Sahand

+0

'' sync.WaitGroup' informuje kompilator, który ma mieć wskaźnik do 'WaitGroup' zamiast' WaitGroup'. Tak więc kompilator oczekuje, że wywołasz metodę, podając jej adres, stąd '& wg'. – Elwinar

+2

Nie myśl, że możesz usunąć grupę roboczą drukowania jako główną, która mogłaby się przedawnić, zanim ostatnia wartość zostanie wydrukowana, jeśli oczekiwałeś na zakończenie nadawcy. Ponadto, nie ma powodu, aby zakończyć funkcję z opóźnieniem wg.Done(), odradzam w zasadzie oznacza, uruchom to na końcu. upuść odroczenie –

1

Zgadzam się z rozwiązaniem @ Elwinar, że głównym problemem w twoim kodzie jest przekazanie kopii twojej Waitgroup do funkcji Print.

Oznacza to, że jest obsługiwany na kopii wg określonej w main. Dlatego wg w main nie można zmniejszyć, a tym samym impasu dzieje się, gdy wg.Wait() w głównej.

Ponieważ są również z prośbą o najlepszych praktyk, mogę podać kilka sugestii moja własna:

  • Nie usuwać defer wg.Done() w Print. Ponieważ twoja goroutine w głównej jest nadawcą, a print jest odbiornikiem, usunięcie wg.Done() w rutynie odbiornika spowoduje niedokończony odbiornik. Dzieje się tak dlatego, że tylko twój nadawca jest zsynchronizowany z twoim głównym, więc po tym, jak twój nadawca się zakończy, twoja główna sprawa jest skończona, ale możliwe, że odbiornik nadal działa. Chodzi mi o to, że: po zakończeniu głównej rutyny nie pozostawiaj zwisających goryli. Zamknij je lub poczekaj na nich.

  • Pamiętaj, aby wszędzie wykonywać remonty antynarkotykowe, szczególnie anonimowe goryle. Widziałem wielu programistów golang, którzy zapomnieli o przywróceniu paniki w gorutynach, nawet jeśli pamiętają, by przywrócić normalne funkcje. Ma to kluczowe znaczenie, gdy chcesz, aby kod zachowywał się poprawnie lub przynajmniej z wdziękiem, gdy wydarzyło się coś nieoczekiwanego.

  • Zastosowanie defer przed każdym krytycznym połączeń, takich jak sync dotyczących połączeń, na początku, ponieważ nie wiem, gdzie kod może pęknąć. Załóżmy, że usunąłeś defer przed wg.Done(), a panika pojawiła się w twoim anonimowym goroutine w twoim przykładzie.Jeśli nie odzyskasz paniki, wpadnie ona w panikę. Ale co się stanie, jeśli panika wyzdrowieje? Teraz wszystko w porządku? Nie. Otrzymasz impas w numerze wg.Wait(), ponieważ Twoja wg.Done() zostanie pominięta z powodu paniki! Jednakże, używając defer, ta wg.Done() zostanie wykonana na końcu, nawet jeśli wystąpi panika. Również odroczenie przed close jest również ważne, ponieważ jego wynik wpływa również na komunikację.

Więc tutaj jest kod zmodyfikowany zgodnie z punktami I wymienionych powyżej:

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

nadzieję, że to pomaga :)

+0

Wielkie dzięki! – Sahand

+0

Całkowicie nie zgadzam się z _Nie usuwaj wg z Print_. Ale może moja propozycja nie została poprawnie zrozumiana, radzę zrobić coś takiego: http://play.golang.org/p/lOopY0bYHT W ten sposób Twoja rutyna nie wie, że jest zsynchronizowana, co czyni to prostszym. Z reguły, * chyba że synchronizacja jest częścią algorytmu, twój kod nigdy nie powinien być tego świadomy *. Co oznacza, że ​​metoda drukowania czegoś na ekranie nigdy nie będzie wymagać żadnej świadomości synchronizacji. – Elwinar

+0

To dobry pomysł, aby wyleczyć z paniki, ale w tym przykładzie jest to przesada. Jedyną rzeczą, która może panikować, jest wysyłanie do zamkniętego kanału, a ty to zamykasz. Nie wpadnij w pułapkę sprawdzania wszystkiego za każdym razem, to będzie po prostu nadpisywać twój kod niepotrzebnymi czekami i utrudniać czytanie. – Elwinar