sync.WaitGroup의 예가 맞습니까?


108

이 예제 사용법이 sync.WaitGroup맞습니까? 예상 된 결과를 제공하지만의 wg.Add(4)및 위치에 대해 잘 모르겠습니다 wg.Done(). 4 개의 고 루틴을 한 번에 추가하는 것이 합리적 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.Done ()을 수행하기 전에 dosomething ()이 충돌하면 어떻게됩니까?
Mostowski Collapse

19
나는 이것이 오래되었다는 것을 알고 있지만 미래의 사람들 defer wg.Done()을 위해 함수를 시작할 때 초기 호출을 권장 합니다.
Brian

답변:


154

예,이 예가 맞습니다. 경합 상태를 방지 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카운터가 0 아래로 떨어지면 패닉. 카운터는 0에서 시작하고 각각 Done()은 a -1이며 각각 Add()은 매개 변수에 따라 다릅니다. 그래서, 카운터가 결코 아래로 떨어질없고 피할 패닉 수 있도록, 당신은이 필요로 Add()하는 보장 전과 와서 Done().

Go에서 이러한 보장은 메모리 모델에 의해 제공됩니다 .

메모리 모델은 단일 고 루틴의 모든 명령문이 작성된 순서대로 실행되는 것으로 나타납니다. 실제로 그 순서가 아닐 수도 있지만 결과는 마치 그럴 것입니다. 또한 goroutine은이 go를 호출 하는 문이 끝날 때까지 실행되지 않습니다 . (가)부터 Add()전과 발생 go문과 go문이 전에 발생 Done(), 우리는 알고 Add()전과 발생합니다 Done().

go명령문을 앞에 올 Add()경우 프로그램이 올바르게 작동 할 수 있습니다. 그러나 보장되지 않으므로 경쟁 조건이됩니다.


9
이 질문에 대해 질문이 있습니다 defer wg.Done(). 고 루틴이 취하는 경로에 관계없이 호출되도록하는 것이 더 낫지 않을까요? 감사.
Alessandro Santini

2
모든 go 루틴이 완료되기 전에 함수가 반환되지 않았는지 확인하고 싶다면 yes defer가 선호됩니다. 일반적으로 대기 그룹의 전체 요점은 모든 작업이 완료 될 때까지 기다린 다음 기다리고 있던 결과로 무언가를하는 것입니다.
Zanven

1
사용하지 않고 defer고 루틴 중 하나가 호출에 실패하면 wg.Done()... Wait단순히 차단 하지 않겠습니까? 찾기 어려운 버그를 코드에 쉽게 도입 할 수있는 것처럼 들립니다 ...
댄 에스파 르자에게

29

함수 자체에 wg.Add()호출을 임베드하는 것이 좋습니다. 호출 doSomething()횟수를 조정하는 경우 추가 매개 변수를 수동으로 조정하지 않아도됩니다. 이렇게하면 매개 변수를 업데이트하지만 업데이트하는 것을 잊으면 오류가 발생할 수 있습니다. 기타 (이 사소한 예제에서는 가능성이 낮지 만 여전히 코드 재사용을위한 더 나은 방법이라고 생각합니다.)

Stephen Weinberg 가이 질문대한 답변 에서 지적했듯이 gofunc 를 생성 하기 전에 waitgroup을 증가 시켜야하지만 다음 doSomething()과 같이 함수 자체 내부에 gofunc 생성을 래핑하여 쉽게 수행 할 수 있습니다 .

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_


21
  • Mroth 답변을 기반으로 한 작은 개선
  • Done에 대해 지연을 사용하는 것이 더 안전합니다.
  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")
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.