1. 程式人生 > 其它 >(轉)Go sync.WaitGroup的用法

(轉)Go sync.WaitGroup的用法

介紹

經常會看到以下了程式碼:

package main

import (
    "fmt"
    "time"
)

func main(){
    for i := 0; i < 100 ; i++{
        go fmt.Println(i)
    }
    time.Sleep(time.Second)
}

主執行緒為了等待goroutine都執行完畢,不得不在程式的末尾使用time.Sleep()來睡眠一段時間,等待其他執行緒充分執行。對於簡單的程式碼,100個for迴圈可以在1秒之內執行完畢,time.Sleep()也可以達到想要的效果。

但是對於實際生活的大多數場景來說,1秒是不夠的,並且大部分時候我們都無法預知for迴圈內程式碼執行時間的長短。這時候就不能使用time.Sleep()

來完成等待操作了。

可以考慮使用管道來完成上述操作:

func main() {
    c := make(chan bool, 100)
    for i := 0; i < 100; i++ {
        go func(i int) {
            fmt.Println(i)
            c <- true
        }(i)
    }

    for i := 0; i < 100; i++ {
        <-c
    }
}
首先可以肯定的是使用管道是能達到我們的目的的,而且不但能達到目的,還能十分完美的達到目的。

但是管道在這裡顯得有些大材小用,因為它被設計出來不僅僅只是在這裡用作簡單的同步處理,在這裡使用管道實際上是不合適的。而且假設我們有一萬、十萬甚至更多的for

迴圈,也要申請同樣數量大小的管道出來,對記憶體也是不小的開銷。

對於這種情況,go語言中有一個其他的工具sync.WaitGroup能更加方便的幫助我們達到這個目的。

WaitGroup物件內部有一個計數器,最初從0開始,它有三個方法:Add(), Done(), Wait()用來控制計數器的數量。Add(n)把計數器設定為nDone()每次把計數器-1wait()會阻塞程式碼的執行,直到計數器地值減為0

使用WaitGroup將上述程式碼可以修改為:

func main() {
    wg := sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go func(i int) {
            fmt.Println(i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

這裡首先把wg 計數設定為100, 每個for迴圈執行完畢都把計數器減一,主函式中使用Wait() 一直阻塞,直到wg為零——也就是所有的100個for迴圈都執行完畢。相對於使用管道來說,WaitGroup 輕巧了許多。

注意事項

1. 計數器不能為負值

我們不能使用Add()wg設定一個負值,否則程式碼將會報錯:

panic: sync: negative WaitGroup counter

goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
    D:/Go/src/sync/waitgroup.go:75 +0x1d0
main.main()
    D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54

同樣使用Done()也要特別注意不要把計數器設定成負數了。

2. WaitGroup物件不是一個引用型別

WaitGroup物件不是一個引用型別,在通過函式傳值的時候需要使用地址:

func main() {
    wg := sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go f(i, &wg)
    }
    wg.Wait()
}

// 一定要通過指標傳值,不然程序會進入死鎖狀態
func f(i int, wg *sync.WaitGroup) { 
    fmt.Println(i)
    wg.Done()
}

轉自:https://studygolang.com/articles/12972