Пример для sync.WaitGroup правильно?
Правильно ли это пример использования sync.WaitGroup
? Он дает ожидаемый результат, но я не уверен в wg.Add(4)
и позиции wg.Done()
. Имеет ли смысл добавить четыре гортани сразу с помощью 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")
}
Результат (как и ожидалось):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
Ответы
Ответ 1
Да, этот пример верен. Важно, чтобы wg.Add()
выполнялся перед оператором go
для предотвращения условий гонки. Также было бы правильно:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
Однако бессмысленно называть wg.Add
снова и снова, когда вы уже знаете, сколько раз он будет вызываться.
Waitgroups
паника, если счетчик опускается ниже нуля. Счетчик начинается с нуля, каждый Done()
равен -1
, и каждый Add()
зависит от параметра. Таким образом, вам нужно Add()
гарантировать, что оно будет перед Done()
, чтобы избежать паники.
В Go такие гарантии даются моделью .
В модели памяти указано, что все операторы в одной версии goroutine выполняются в том же порядке, в каком они записаны. Вполне возможно, что на самом деле они не будут в таком порядке, но результат будет таким, каким он есть. Также гарантируется, что goroutine не запускается до тех пор, пока оператор go
, который его вызывает. Поскольку Add()
встречается перед оператором go
, а оператор go
встречается до Done()
, мы знаем, что Add()
встречается перед Done()
.
Если вы указали инструкцию go
перед Add()
, программа может работать правильно. Однако это было бы соревнование, потому что это не гарантировалось.
Ответ 2
Я бы рекомендовал встраивать вызов wg.Add()
в функцию doSomething()
, так что, если вы отрегулируете количество раз, которое оно вызывало, вам не нужно отдельно настраивать параметр добавления вручную, что может привести к ошибке если вы обновите его, но забудьте обновить другое (в этом тривиальном примере это маловероятно, но все же я лично считаю, что это лучше практика повторного использования кода).
Как говорит Стивен Вайнберг в его ответе на этот вопрос, вам нужно увеличить ожидающую группу до появления gofunc, но вы можете сделать это легко, обернув gofunc появляется внутри самой функции doSomething()
, например:
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}()
}
Затем вы можете вызвать его без вызова go
, например:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
Как детская площадка: http://play.golang.org/p/WZcprjpHa_