等待所有goroutines执行完成后再结束程序
goroutines
当一个go程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine。
新的 goroutine 会用 go 语句来创建。在语法上, go 语句是一个普通的函数或方法调用前加上关键字 go 。 go 语句会使其语句中的函数在一个新创建的 goroutine 中运行。
一定要注意的是, goroutine 以非阻塞的方式执行,它们会随着程序(main goroutine)的结束而消亡。所以在使用 goroutine 时,有必要采取一定的手段使得程序等待所有 goroutines 执行完成后再结束。
下面介绍三种方式来实现这一效果。
使用sleep
在主程序结束前的位置添加下面的代码,让程序等待一段时间再退出。
time.Sleep(100 * time.Millisecond)
time.Sleep
默认的单位是纳秒,这里指定了时间单位为毫秒。
这种方式非常傻,因为无法预知所有 goroutines 需要执行多久才能结束,等待的时间长了会验证影响程序的执行效率,而时间短了又无法确保所有 goroutines 都执行完成。
使用chanel
在《Go语言编程》这本书中介绍过。使用Channel通信来解决,goruntime没有执行完,main就退出的问题。简单示例如下:
package main
import "fmt"
func download(url string, ch chan int) {
fmt.Println(url)
ch <- 1
}
func main() {
urls := []string{
"http://www.a.com/1.gzip",
"http://www.a.com/2.gzip",
"http://www.a.com/3.gzip",
"http://www.a.com/4.gzip",
}
ch := make(chan int, 4)
for _, v := range urls {
go download(v, ch)
}
for i := 0; i < len(urls); i++ {
<-ch
}
fmt.Println("over")
}
chanel能实现的功能还有很多,今后专门介绍。
sync
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 实例,比如名称为:wg
- 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
- 在每个 goroutine 运行的函数中执行 defer wg.Done()
- 调用 wg.Wait() 阻塞主逻辑
var wg sync.WaitGroup
...
for ... {
...
wg.Add(1)
go func(...) {
defer wg.Done()
...
}
}
wg.Wait()
下面是一个稍稍真实一点的例子,检查请求网站的返回状态。如果要在收到所有的结果后进一步处理这些返回状态,就需要等待所有的请求结果返回:
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
}