Goroutine 是 Golang 中很是有用的功能,可是在使用中咱們常常碰到下面的場景:若是但願等待當前的 goroutine 執行完成,而後再接着往下執行,該怎麼辦?本文嘗試介紹這類問題的解決方法。golang
讓咱們運行下面的代碼,並關注輸出的結果:函數
package main import ( "time" "fmt" ) func say(s string) { for i := 0; i < 3; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("hello world") fmt.Println("over!") }
輸出的結果爲:
over!
由於 goroutine 以非阻塞的方式執行,它們會隨着程序(主線程)的結束而消亡,因此程序輸出字符串 "over!" 就退出了,這可不是咱們想要的結果。fetch
要解決上面的問題,最簡單、直接的方式就是經過 Sleep 函數死等 goroutine 執行完成:網站
func main() { go say("hello world") time.Sleep(1000 * time.Millisecond) fmt.Println("over!") }
運行修改後的程序,結果以下:
hello world
hello world
hello world
over!
結果符合預期,可是太 low 了,咱們不知道實際執行中應該等待多長時間,因此不能接受這個方案!ui
經過 channel 也能夠達到等待 goroutine 結束的目的,運行下面的代碼:url
func main() { done := make(chan bool) go func() { for i := 0; i < 3; i++ { time.Sleep(100 * time.Millisecond) fmt.Println("hello world") } done <- true }() <-done fmt.Println("over!") }
輸出的結果也是:
hello world
hello world
hello world
over!
這種方法的特色是執行多少次 done <- true 就得執行多少次 <-done,因此也不是優雅的解決方式。spa
Golang 官方在 sync 包中提供了 WaitGroup 類型來解決這個問題。其文檔描述以下:.net
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.
大意爲:WaitGroup 用來等待單個或多個 goroutines 執行結束。在主邏輯中使用 WaitGroup 的 Add 方法設置須要等待的 goroutines 的數量。在每一個 goroutine 執行的函數中,須要調用 WaitGroup 的 Done 方法。最後在主邏輯中調用 WaitGroup 的 Wait 方法進行阻塞等待,直到全部 goroutine 執行完成。
使用方法能夠總結爲下面幾點:線程
運行下面的代碼:code
package main import ( "time" "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(2) say2("hello", &wg) say2("world", &wg) fmt.Println("over!") } func say2(s string, waitGroup *sync.WaitGroup) { defer waitGroup.Done() for i := 0; i < 3; i++ { fmt.Println(s) } }
輸出的結果以下:
hello
hello
hello
world
world
world
over!
下面是一個稍稍真實一點的例子,檢查請求網站的返回狀態。若是要在收到全部的結果後進一步處理這些返回狀態,就須要等待全部的請求結果返回:
package main import ( "fmt" "sync" "net/http" ) func main() { var urls = []string{ "https://www.baidu.com/", "https://www.cnblogs.com/", } var wg sync.WaitGroup for _, url := range urls { wg.Add(1) go fetch(url, &wg) } wg.Wait() } func fetch(url string, wg *sync.WaitGroup) (string, error) {
defer wg.Done() resp, err := http.Get(url) if err != nil { fmt.Println(err) return "", err }
fmt.Println(resp.Status) return resp.Status, nil }
運行上面的代碼,輸出的結果以下:
200 OK
200 OK
參考:
How to Wait for All Goroutines to Finish Executing Before Continuing
Go WaitGroup Tutorial