如何使用WaitGroup在Golang中等待goroutines完成

在本文中,我们展示了如何使用WaitGroup在Golang中等待goroutines完成。

$ go version
go version go1.18.1 linux/amd64

我们使用Go版本1.18。

Goroutine是一个轻量级的执行线程。它是一个与其他正在运行的代码同时运行的函数。

WaitGroup等待一组goroutine完成。它充当一个计数器,保存要等待的函数或goroutine的数量。WaitGroup是同步包的一部分。

等待组函数

WaitGroup具有三个函数:AddDoneWait

func (wg *WaitGroup) Add(delta int)

Add函数将增量值添加到WaitGroup计数器。如果计数器变为零,则释放所有阻塞在Wait上的goroutine。

func (wg *WaitGroup) Done()

DoneWaitGroup计数器减一。当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教程。

赞(0) 打赏

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏