Golang 入门 : 等待 goroutine 完成任务

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 函数等待

要解决上面的问题,最简单、直接的方式就是经过 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

经过 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 执行完成。
使用方法能够总结为下面几点:线程

  1. 建立一个 WaitGroup 实例,好比名称为:wg
  2. 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
  3. 在每一个 goroutine 运行的函数中执行 defer wg.Done()
  4. 调用 wg.Wait() 阻塞主逻辑

运行下面的代码: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

相关文章
相关标签/搜索