golang sync.WaitGroup 用法

執行一個簡單協程

package main

import (
    "fmt"
    "time"
)

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

分析:

爲什麼會有sleep 呢, 主線程爲了等待goroutine都運行完畢, 不得不在程序的末尾使用time.Sleep()來睡眠一段時間, 等待 其他線程充分運行 。對於簡單的代碼100 個for 循環可以在1秒內運行完但是實際的場景中大部分是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 開始, 他有3個方法 Add() , Done(), Wait()用來控制計數器的數量。 Add(n) 把計數器設置成n, Done() 每次把計數器-1, wait() 會阻塞代碼的運行, 直到計數器的值減爲0

將上面代碼修改:

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

這裏計數器設置爲 100 每個for 循環運行完畢都把計數器減1 主函數中使用Wait() 一直阻塞, 直到wg爲0 也就是所有的 100 個for 循環都運行完畢, 相對於使用 管道來說 WaitGroup輕巧了很多。

注意的事項 使用waitGroup

計數器不能爲負

我們不能使用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() 也要特別注意不能把計數器的值設置爲負值

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()
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章