Подтвердить что ты не робот

Пример для 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
4b9b3361

Ответ 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_