正常情況下,新激活的goroutine的結束過程是不可控制的,唯一可以保證終止goroutine的行為是main goroutine的終止。也就是說,我們並不知道哪個goroutine什麼時候結束。 但很多情況下,我們正需要知道goroutine是否完成。這需要藉助sync包的WaitGroup來實 ...
正常情況下,新激活的goroutine的結束過程是不可控制的,唯一可以保證終止goroutine的行為是main goroutine的終止。也就是說,我們並不知道哪個goroutine什麼時候結束。
但很多情況下,我們正需要知道goroutine是否完成。這需要藉助sync包的WaitGroup來實現。
WatiGroup是sync包中的一個struct類型,用來收集需要等待執行完成的goroutine。下麵是它的定義:
type WaitGroup struct {
// Has unexported fields.
}
A WaitGroup waits for a collection of goroutines to finish. The main
goroutine calls Add to set the number of goroutines to wait for. Then each
of the goroutines runs and calls Done when finished. At the same time, Wait
can be used to block until all goroutines have finished.
A WaitGroup must not be copied after first use.
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
它有3個方法:
- Add():每次激活想要被等待完成的goroutine之前,先調用Add(),用來設置或添加要等待完成的goroutine數量
- 例如Add(2)或者兩次調用Add(1)都會設置等待計數器的值為2,表示要等待2個goroutine完成
- 例如Add(2)或者兩次調用Add(1)都會設置等待計數器的值為2,表示要等待2個goroutine完成
- Done():每次需要等待的goroutine在真正完成之前,應該調用該方法來人為表示goroutine完成了,該方法會對等待計數器減1
- Wait():在等待計數器減為0之前,Wait()會一直阻塞當前的goroutine
也就是說,Add()用來增加要等待的goroutine的數量,Done()用來表示goroutine已經完成了,減少一次計數器,Wait()用來等待所有需要等待的goroutine完成。
下麵是一個示例,通過示例很容易理解。
package main
import (
"fmt"
"sync"
"time"
)
func process(i int, wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep(2 * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
wg.Done()
}
func main() {
no := 3
var wg sync.WaitGroup
for i := 0; i < no; i++ {
wg.Add(1)
go process(i, &wg)
}
wg.Wait()
fmt.Println("All go routines finished executing")
}
上面激活了3個goroutine,每次激活goroutine之前,都先調用Add()方法增加一個需要等待的goroutine計數。每個goroutine都運行process()函數,這個函數在執行完成時需要調用Done()方法來表示goroutine的結束。激活3個goroutine後,main goroutine會執行到Wait(),由於每個激活的goroutine運行的process()都需要睡眠2秒,所以main goroutine在Wait()這裡會阻塞一段時間(大約2秒),當所有goroutine都完成後,等待計數器減為0,Wait()將不再阻塞,於是main goroutine得以執行後面的Println()。
還有一點需要特別註意的是process()中使用指針類型的*sync.WaitGroup
作為參數,這裡不能使用值類型的sync.WaitGroup
作為參數,因為這意味著每個goroutine都拷貝一份wg,每個goroutine都使用自己的wg。這顯然是不合理的,這3個goroutine應該共用一個wg,才能知道這3個goroutine都完成了。實際上,如果使用值類型的參數,main goroutine將會永久阻塞而導致產生死鎖。