在本文中,我们展示了如何使用WaitGroup在Golang中等待goroutines完成。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
Goroutine是一个轻量级的执行线程。它是一个与其他正在运行的代码同时运行的函数。
WaitGroup等待一组goroutine完成。它充当一个计数器,保存要等待的函数或goroutine的数量。WaitGroup是同步包的一部分。
等待组函数
WaitGroup具有三个函数:Add、Done和Wait。
func (wg *WaitGroup) Add(delta int)
Add函数将增量值添加到WaitGroup计数器。如果计数器变为零,则释放所有阻塞在Wait上的goroutine。
func (wg *WaitGroup) Done()
Done将WaitGroup计数器减一。当goroutine完成执行时调用它。
func (wg *WaitGroup) Wait()
Wait阻塞,直到WaitGroup计数器为零。
运行协程
主程序本身就是一个协程。它可能比它调用的goroutines更早完成。
package main
import (
    "fmt"
)
func f1() {
    fmt.Println("goroutine 1")
}
func f2() {
    fmt.Println("goroutine 2")
}
func main() {
    go f1()
    go f2()
}
在程序中,我们在main函数中启动了两个goroutine。但是,程序在两个goroutine之前完成。
$ go run main.go
程序不打印任何输出。
package main
import (
    "fmt"
    "time"
)
func f1() {
    fmt.Println("goroutine 1")
}
func f2() {
    fmt.Println("goroutine 2")
}
func main() {
    go f1()
    go f2()
    time.Sleep(2 * time.Second)
}
当我们使用time.Sleep休眠两秒钟时,goroutines有时间完成。
$ go run main.go goroutine 2 goroutine 1
GoWaitGroup简单例子
sync.WaitGroup是一个等待goroutine集合完成的同步工具。
package main
import (
    "fmt"
    "sync"
    "time"
)
func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        count("oranges")
        wg.Done()
    }()
    go func() {
        count("bananas")
        wg.Done()
    }()
    wg.Wait()
}
func count(thing string) {
    for i := 0; i < 4; i++ {
        fmt.Printf("counting %s\n", thing)
        time.Sleep(time.Millisecond * 500)
    }
}
我们使用WaitGroup同步两个goroutine的执行。
var wg sync.WaitGroup wg.Add(2)
通过Add函数,我们可以告诉我们等待的goroutines有多少。
go func() {
    count("oranges")
    wg.Done()
}()
我们创建一个匿名goroutine。我们用Done告诉Go运行时goroutine已经完成。
wg.Wait()
Wait函数会阻塞,直到所有goroutine完成。
time.Sleep(time.Millisecond * 500)
在演示程序中,time.Sleep通常用于减慢goroutines的执行速度。
$ go run main.go counting bananas counting oranges counting oranges counting bananas counting bananas counting oranges counting oranges counting bananas
我们必须在函数中传递一个指向WaitGroup的指针。
package main
import (
    "fmt"
    "sync"
)
func f1(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("goroutine 1")
}
func f2(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("goroutine 2")
}
func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go f1(&wg)
    wg.Add(1)
    go f2(&wg)
    wg.Wait()
}
在示例中,我们有两个goroutine。我们向它们传递一个指向WaitGroup的指针。
GoWaitGroup异步HTTP请求
在下面的示例中,我们使用goroutines进行多个异步HTTP请求。
package main
import (
    "fmt"
    "log"
    "net/http"
    "sync"
)
func main() {
    urls := []string{
        "http://webcode.me",
        "https://example.com",
        "http://httpbin.org",
        "https://www.perl.org",
        "https://www.python.org",
        "https://clojure.org",
    }
    var wg sync.WaitGroup
    for _, u := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            status := doReq(url)
            fmt.Printf("%s - %s\n", url, status)
        }(u)
    }
    wg.Wait()
}
func doReq(url string) (status string) {
    resp, err := http.Head(url)
    if err != nil {
        log.Println(err)
        return
    }
    return resp.Status
}
我们发出多个异步HTTP请求。我们发出HEAD请求并返回响应的状态代码。每个请求都包装在一个goroutine中。
var wg sync.WaitGroup
WaitGroup用于等待所有请求完成。
for _, u := range urls {
    wg.Add(1)
    go func(url string) {
        ...
    }(u)
}
我们遍历url的一部分并向计数器添加一个goroutine。
go func(url string) {
    defer wg.Done()
    status := doReq(url)
    fmt.Printf("%s - %s\n", url, status)
}(u)
使用goroutine,我们生成一个HEAD请求,接收响应,并打印状态代码。请求完成后,调用Done函数来减少计数器。
$ go run main.go http://webcode.me - 200 OK https://www.python.org - 200 OK https://www.perl.org - 200 OK https://clojure.org - 200 OK http://httpbin.org - 200 OK https://example.com - 200 OK
在本文中,我们使用了WaitGroup来等待goroutine的集合完成。
列出所有Go教程。
