Пример для синхронизации.WaitGroup правильно?


это пример использования sync.WaitGroup исправить? Это дает ожидаемый результат, но я не уверен, о wg.Add(4) и позицию wg.Done(). Имеет ли смысл добавить четыре goroutines сразу с 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
3 78

3 ответа:

Да, этот пример верен. Важно, что 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, как представляется, выполняются в том же порядке, как они записаны. Возможно, что они на самом деле не будут в таком порядке, но результат будет таким, как если бы он был. Также гарантируется, что A goroutine не работает до тех пор, пока после go заявление, которое называет его. Так как Add() возникает перед go заявления и go утверждение происходит перед Done(), мы знаем Add() возникает перед Done().

если бы у вас был go заявление перед Add() программа может работать правильно. Однако это будет условие гонки, потому что оно не будет гарантировано.

Я бы рекомендовал вставить wg.Add() вызов doSomething() сама функция, так что если вы отрегулируете количество раз, когда она вызывается, вам не нужно отдельно настраивать параметр add вручную, что может привести к ошибке, если вы обновите один, но забудете обновить другой (в этом тривиальном примере это маловероятно, но все же я лично считаю, что это лучшая практика для повторного использования кода).

как указывает Стивен Вайнберг в его ответ на это вопрос, вы должны увеличить группу ожидания до для нереста 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_

  • небольшое улучшение на основе ответа Mroth
  • использование отложить для сделано безопаснее
  func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
  wg.Add(1)
  go func() {
      defer wg.Done()
      duration := millisecs * time.Millisecond
      time.Sleep(duration)
      fmt.Println("Function in background, duration:", duration)
  }()
}

func main() {
  var wg sync.WaitGroup
  dosomething(200, &wg)
  dosomething(400, &wg)
  dosomething(150, &wg)
  dosomething(600, &wg)
  wg.Wait()
  fmt.Println("Done")
}