Golang sync.WaitGroup

Go語言中除了可使用通道(channel)和互斥鎖進行兩個併發程序間的同步外,還可使用等待組進行多個任務的同步等待組能夠保證在併發環境中完成指定數量的任務git

在 sync.WaitGroup(等待組)類型中,每一個 sync.WaitGroup 值在內部維護着一個計數,此計數的初始默認值爲零。golang

等待組有下面幾個方法可用,以下所示。併發

  • func (wg *WaitGroup) Add(delta int): 等待組的計數器 +1
  • func (wg *WaitGroup) Done(): 等待組的計數器 -1
  • func (wg *WaitGroup) Wait(): 當等待組計數器不等於 0 時阻塞直到變 0。

對於一個可尋址的 sync.WaitGroup 值 wg:函數

  • 咱們可使用方法調用 wg.Add(delta) 來改變值 wg 維護的計數。
  • 方法調用 wg.Done() 和 wg.Add(-1) 是徹底等價的。
  • 若是一個 wg.Add(delta) 或者 wg.Done() 調用將 wg 維護的計數更改爲一個負數,將會產生 panic 異常。
  • 當一個協程調用了 wg.Wait() 時,
    • 若是此時 wg 維護的計數爲零,則此 wg.Wait() 此操做爲一個空操做(noop);
    • 不然(計數爲一個正整數),此協程將進入阻塞狀態。當之後其它某個協程將此計數更改至 0 時(通常經過調用 wg.Done()),此協程將從新進入運行狀態(即 wg.Wait() 將返回)。

等待組內部擁有一個計數器,計數器的值能夠經過方法調用實現計數器的增長和減小。當咱們添加了 N 個併發任務進行工做時,就將等待組的計數器值增長 N。每一個任務完成時,這個值減 1。同時,在另一個 goroutine 中等待這個等待組的計數器值爲 0 時,表示全部任務已經完成。oop

什麼意思?咱們先來回憶一下以前咱們爲了保證子 go 程運行完畢,主 go 程是怎麼作的:網站

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        fmt.Println("Goroutine 1")
    }()

    go func() {
        fmt.Println("Goroutine 2")
    }()

    time.Sleep(time.Second) // 睡眠 1 秒,等待上面兩個子 go 程結束
}

咱們爲了讓子 go 程能夠順序的執行完,在主 go 程中加入了等待。咱們知道,這不是一個很好的解決方案,能夠用 channel 來實現同步:code

package main

import (
    "fmt"
)

func main() {

    ch := make(chan struct{})
    count := 2 // count 表示活動的 go 程個數

    go func() {
        fmt.Println("Goroutine 1")
        ch <- struct{}{} // go 程結束,發出信號
    }()

    go func() {
        fmt.Println("Goroutine 2")
        ch <- struct{}{} // go 程結束,發出信號
    }()

    for range ch {
        // 每次從 ch 中接收數據,代表一個活動的 go 程結束
        count--
        // 當全部活動的 go 程都結束時,關閉 channel
        if count == 0 {
            close(ch)
        }
    }
}

上面的解決方案是雖然已經比較好了,可是 Go 提供了更簡單的方法:使用 sync.WaitGroup協程

package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup

    wg.Add(2) // 由於有兩個動做,因此增長 2 個計數
    go func() {
        fmt.Println("Goroutine 1")
        wg.Done() // 操做完成,減小一個計數
    }()

    go func() {
        fmt.Println("Goroutine 2")
        wg.Done() // 操做完成,減小一個計數
    }()

    wg.Wait() // 等待,直到計數爲0
}

可見用 sync.WaitGroup 是最簡單的方式。對象

強調一下:文檔

  • 計數器不能爲負值:不能使用 Add() 或者 Done() 給 wg 設置一個負值,不然代碼將會報錯。
  • WaitGroup 對象不是一個引用類型:在經過函數傳值的時候須要使用地址。

官方文檔看這裏:https://golang.org/pkg/sync/#WaitGroup

練習題

一、寫代碼實現兩個 goroutine,其中一個產生隨機數並寫入到 go channel 中,另一個從 channel 中讀取數字並打印到標準輸出。最終輸出五個隨機數。

package main

import (
	"fmt"
	"math/rand"
	"sync"
)

func main() {
	ch := make(chan int)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 5; i++ {
			ch <- rand.Int()
		}
		close(ch)
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		for num := range ch {
			fmt.Println("num = ", num)
		}
	}()
	wg.Wait()
}

李培冠博客

歡迎訪問個人我的網站:

李培冠博客:lpgit.com

相關文章
相關標籤/搜索