Sync.WaitGroup için örnek doğru mu?


108

Bu örnek kullanım sync.WaitGroupdoğru mu? Beklenen sonucu verir, ancak wg.Add(4)konumu ve konumu konusunda emin değilim wg.Done(). Dört gorutini aynı anda eklemek mantıklı mı 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")
}

Sonuç (beklendiği gibi):

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done

1
Ya dosomething () wg.Done () yapmadan önce çökerse?
Mostowski Çöküşü

19
Bunun eski olduğunun farkındayım, ancak gelecekteki insanlar için, defer wg.Done()işlevin başlangıcında ilk görüşmeyi tavsiye ederim .
Brian

Yanıtlar:


154

Evet, bu örnek doğrudur. Yarış koşullarını önlemek wg.Add()için goaçıklamadan önce gerçekleşmesi önemlidir . Aşağıdakiler de doğru olacaktır:

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")
}

Bununla birlikte, wg.Addkaç kez aranacağını bildiğiniz halde tekrar tekrar aramak oldukça anlamsızdır .


Waitgroupssayaç sıfırın altına düşerse paniğe kapılır. Sayacı, sıfır, her bir Done()a, -1ve her bir Add()parametreye bağlıdır. Yani, sayaç asla altına düştüğünde ve önlemek panikler sağlamak için, size gereken Add()edilecek garantili önce gelmek Done().

Go'da bu tür garantiler bellek modeli tarafından verilmektedir .

Bellek modeli, tek bir gruptaki tüm ifadelerin yazıldıkları sırada aynı sırada yürütüldüğünü belirtir. Aslında bu sırada olmayacakları mümkündür, ancak sonuç sanki öyleymiş gibi olacaktır. Ayrıca bir gorutinin, gokendisini çağıran ifadeden sonrasına kadar çalışmayacağı da garanti edilir . Yana Add()önce meydana godeyimi ve godeyimi önce oluşur Done(), biliyoruz Add()önce gerçekleşir Done().

Eğer var olsaydı godeyimi önce gelir Add(), programın doğru bir şekilde çalışabilirler. Ancak, garanti edilemeyeceği için yarış koşulu olacaktır.


9
Bununla ilgili bir sorum var: defer wg.Done()Gorutinin gittiği yoldan bağımsız olarak çağrılacağından emin olmak daha iyi olmaz mı? Teşekkürler.
Alessandro Santini

2
tüm go rutinleri tamamlanmadan önce işlevin geri dönmediğinden emin olmak istiyorsanız, evet erteleme tercih edilecektir. sadece genellikle bir bekleme grubunun tüm amacı, tüm iş bitene kadar beklemek ve ardından beklediğiniz sonuçlarla bir şeyler yapmaktır.
Zanven

1
Eğer kullanmazsan deferve gorutinlerinden biri aramazsa wg.Done()... Waitsonsuza dek bloke olmaz mısın? Bu, kodunuza kolayca bulunması zor bir hata ekleyebilecek gibi görünüyor ...
Dan Esparza

29

wg.Add()Çağrıyı doSomething()işlevin içine yerleştirmenizi tavsiye ederim , böylece çağrılma sayısını ayarlarsanız, add parametresini manuel olarak ayrı ayrı ayarlamanıza gerek kalmaz, bu da birini güncellerseniz ancak güncellemeyi unutursanız bir hataya yol açabilir. diğer (bu önemsiz örnekte bu olası değildir, ancak yine de şahsen kodun yeniden kullanımı için daha iyi bir uygulama olduğuna inanıyorum).

Stephen Weinberg'in bu soruya verdiği cevabın işaret ettiği gibi , gofunc'u üretmeden önce bekleme grubunu artırmanız gerekir, ancak bunu, gofunc yumurtasını doSomething()işlevin içine sararak kolayca başarabilirsiniz , örneğin:

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()
    }()
}

Daha sonra çağrı yapmadan çağırabilirsiniz go, örneğin:

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

Oyun alanı olarak: http://play.golang.org/p/WZcprjpHa_


21
  • Mroth cevabına dayalı küçük gelişme
  • Bitti için ertelemeyi kullanmak daha güvenlidir
  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")
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.