Czy to przykładowe użycie jest sync.WaitGroup
prawidłowe? Daje oczekiwany efekt, ale nie jestem pewien co wg.Add(4)
do pozycji i pozycji wg.Done()
. Czy ma sens dodawanie jednocześnie czterech gorutyn wg.Add()
?
http://play.golang.org/p/ecvYHiie0P
package main
import (
"fmt"
"sync"
"time"
)
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(4)
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
Wynik (zgodnie z oczekiwaniami):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
defer wg.Done()
rozmowę na początku funkcji.Odpowiedzi:
Tak, ten przykład jest poprawny. Ważne jest, aby
wg.Add()
zdarzyło się to przedgo
oświadczeniem, aby zapobiec warunkom wyścigu. Poniższe również byłoby poprawne:Jednak nie ma sensu dzwonić
wg.Add
w kółko, kiedy już wiesz, ile razy będzie to wywoływane.Waitgroups
panika, jeśli licznik spadnie poniżej zera. Licznik zaczyna się od zera, każdyDone()
jest a-1
i każdyAdd()
zależy od parametru. Tak więc, aby mieć pewność, że licznik nigdy nie spadnie poniżej i unikniesz paniki, musiszAdd()
mieć pewność, że nastąpi przedDone()
.W Go takie gwarancje daje model pamięci .
Model pamięci stwierdza, że wszystkie instrukcje w jednym gorutynie wydają się być wykonywane w tej samej kolejności, w jakiej zostały zapisane. Możliwe, że tak naprawdę nie będą w tej kolejności, ale wynik będzie taki, jakby był. Gwarantuje się również, że gorutyna nie działa, dopóki nie nastąpi
go
stwierdzenie, które ją woła . Ponieważ wyrażenieAdd()
występuje przedgo
instrukcją, ago
instrukcja występuje przedDone()
, wiemy, żeAdd()
występuje przedDone()
.Jeśli miałbyś mieć
go
oświadczenie poprzedzająceAdd()
, program może działać poprawnie. Byłby to jednak warunek wyścigu, ponieważ nie byłby to gwarantowany.źródło
defer wg.Done()
, abyśmy byli pewni, że zostanie wywołany niezależnie od trasy, którą pokona rutynę? Dzięki.defer
i jedna z twoich gorutyn nie zadzwoniwg.Done()
... czyWait
po prostu nie zablokujesz się na zawsze? Wygląda na to, że może łatwo wprowadzić trudny do znalezienia błąd do twojego kodu ...Zalecałbym
wg.Add()
wbudowanie wywołania dodoSomething()
samej funkcji, aby w przypadku dostosowania liczby wywołań nie trzeba było oddzielnie dostosowywać parametru add, co mogłoby prowadzić do błędu, jeśli go zaktualizujesz, ale zapomnisz zaktualizować inne (w tym trywialnym przykładzie jest to mało prawdopodobne, ale osobiście uważam, że jest to lepsza praktyka ponownego wykorzystania kodu).Jak Stephen Weinberg wskazuje w swej odpowiedzi na to pytanie , trzeba jednak zwiększamy waitgroup przed tarłem na gofunc, ale można to łatwo osiągnąć poprzez owinięcie ikry gofunc wewnątrz
doSomething()
samej funkcji, jak poniżej:Wtedy możesz to wywołać bez
go
wywołania, np:Jako plac zabaw: http://play.golang.org/p/WZcprjpHa_
źródło
źródło