Ví dụ cho sync.WaitGroup đúng không?


108

Cách sử dụng ví dụ này có sync.WaitGroupđúng không? Nó mang lại kết quả mong đợi, nhưng tôi không chắc chắn về wg.Add(4)vị trí của wg.Done(). Có hợp lý khi thêm bốn goroutines cùng một lúc wg.Add()không?

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

Kết quả (như mong đợi):

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

1
Điều gì sẽ xảy ra nếu dosomething () bị lỗi trước khi nó có thể thực hiện được wg.Done ()?
Mostowski Thu gọn vào

19
Tôi nhận thấy điều này đã cũ, nhưng đối với những người trong tương lai, tôi khuyên bạn nên thực hiện một defer wg.Done()lệnh gọi ban đầu khi bắt đầu hàm.
Brian

Câu trả lời:


154

Vâng, ví dụ này là chính xác. Điều quan trọng là wg.Add()xảy ra trước khi gotuyên bố để ngăn chặn các điều kiện chủng tộc. Những điều sau đây cũng sẽ đúng:

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

Tuy nhiên, việc gọi đi gọi lại sẽ rất vô ích wg.Addkhi bạn đã biết nó sẽ được gọi bao nhiêu lần.


Waitgroupshoảng sợ nếu bộ đếm giảm xuống dưới không. Bộ đếm bắt đầu từ 0, mỗi cái Done()là a -1và mỗi cái Add()phụ thuộc vào tham số. Vì vậy, để đảm bảo rằng quầy không bao giờ giảm xuống dưới và tránh hoảng loạn, bạn cần phải Add()được đảm bảo để đến trước Done().

Trong cờ vây, những đảm bảo như vậy được đưa ra bởi mô hình bộ nhớ .

Mô hình bộ nhớ cho biết rằng tất cả các câu lệnh trong một chương trình goroutine duy nhất dường như được thực hiện theo thứ tự như khi chúng được viết. Có thể chúng sẽ không thực sự theo thứ tự đó, nhưng kết quả sẽ như thể. Nó cũng được đảm bảo rằng một quy trình sẽ không chạy cho đến sau gocâu lệnh gọi nó . Vì câu lệnh Add()xảy ra trước gocâu lệnh và gocâu lệnh xảy ra trước câu lệnh Done(), chúng ta biết điều đó Add()xảy ra trước câu lệnh Done().

Nếu bạn phải có gocâu lệnh trước Add(), chương trình có thể hoạt động chính xác. Tuy nhiên, nó sẽ là một điều kiện của cuộc đua vì nó sẽ không được đảm bảo.


9
Tôi có một câu hỏi về câu hỏi này: sẽ tốt hơn defer wg.Done()nếu chúng ta chắc chắn rằng nó được gọi bất kể tuyến đường mà quy trình thực hiện? Cảm ơn.
Alessandro Santini

2
nếu bạn hoàn toàn muốn đảm bảo rằng hàm không trả về trước khi tất cả các quy trình hoạt động hoàn tất thì bạn nên trì hoãn. Thông thường, toàn bộ điểm của một nhóm chờ đợi là đợi cho đến khi tất cả công việc được hoàn thành để sau đó làm điều gì đó với kết quả mà bạn đã chờ đợi.
Zanven

1
Nếu bạn không sử dụng defervà một trong các tuyến đường của bạn không gọi được wg.Done()... sẽ không Waitđơn giản là chặn mãi mãi? Đây âm thanh như nó có thể dễ dàng giới thiệu một-to-find cứng lỗi vào mã của bạn ...
Dan Esparza

29

Tôi khuyên bạn nên nhúng lệnh wg.Add()gọi vào doSomething()chính hàm, để nếu bạn điều chỉnh số lần nó được gọi, bạn không phải điều chỉnh riêng thông số thêm theo cách thủ công, điều này có thể dẫn đến lỗi nếu bạn cập nhật nhưng quên cập nhật khác (trong ví dụ nhỏ này là không chắc, nhưng cá nhân tôi vẫn tin rằng nó là thực tiễn tốt hơn để sử dụng lại mã).

Như Stephen Weinberg đã chỉ ra trong câu trả lời của mình cho câu hỏi này , bạn phải tăng nhóm chờ trước khi tạo ra gofunc, nhưng bạn có thể thực hiện điều này một cách dễ dàng bằng cách gói gofunc sinh ra bên trong doSomething()chính hàm, như sau:

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

Sau đó, bạn có thể gọi nó mà không cần gogọi, ví dụ:

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

Là một sân chơi: http://play.golang.org/p/WZcprjpHa_


21
  • cải tiến nhỏ dựa trên câu trả lời của Mroth
  • sử dụng trì hoãn cho Xong sẽ an toàn hơn
  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")
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.