Golang 入門 : 等待 goroutine 完成任務


Goroutine 是 Golang 中非常有用的功能,但是在使用中我們經常碰到下面的場景:如果希望等待當前的 goroutine 執行完成,然后再接着往下執行,該怎么辦?本文嘗試介紹這類問題的解決方法。

沒有等待的情況

讓我們運行下面的代碼,並關注輸出的結果:

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!" 就退出了,這可不是我們想要的結果。

使用 Sleep 函數等待

要解決上面的問題,最簡單、直接的方式就是通過 Sleep 函數死等 goroutine 執行完成:

func main() {
    go say("hello world")
    time.Sleep(1000 * time.Millisecond)
    fmt.Println("over!")
}

運行修改后的程序,結果如下:
hello world
hello world
hello world
over!
結果符合預期,但是太 low 了,我們不知道實際執行中應該等待多長時間,所以不能接受這個方案!

使用 channel

通過 channel 也可以達到等待 goroutine 結束的目的,運行下面的代碼:

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,所以也不是優雅的解決方式。

標准答案

Golang 官方在 sync 包中提供了 WaitGroup 類型來解決這個問題。其文檔描述如下:

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 執行完成。
使用方法可以總結為下面幾點:

  1. 創建一個 WaitGroup 實例,比如名稱為:wg
  2. 調用 wg.Add(n),其中 n 是等待的 goroutine 的數量
  3. 在每個 goroutine 運行的函數中執行 defer wg.Done()
  4. 調用 wg.Wait() 阻塞主邏輯

運行下面的代碼:

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


免責聲明!

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



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