Golang sync.WaitGroup


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

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

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

  • 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 時,表示所有任務已經完成。

什么意思?我們先來回憶一下之前我們為了保證子 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 來實現同步:

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

練習題

1、寫代碼實現兩個 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM