Goroutine 是 Golang 中非常有用的功能,但是在使用中我们经常碰到下面的场景:如果希望等待当前的 goroutine 执行完成,然后再接着往下执行,该怎么办?本文尝试介绍这类问题的解决方法。

没有等待的情况

让我们运行下面的代码,并关注输出的结果:

package main

import (
"time"
"fmt"
) func say(s string) {
for i := ; i < ; i++ {
time.Sleep( * 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( * 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 := ; i < ; i++ {
time.Sleep( * 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()
say2("hello", &wg)
say2("world", &wg)
fmt.Println("over!")
} func say2(s string, waitGroup *sync.WaitGroup) {
defer waitGroup.Done() for i := ; i < ; 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()
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

Golang 入门 : 等待 goroutine 完成任务的更多相关文章

  1. Golang 入门 : goroutine(协程)

    在操作系统中,执行体是个抽象的概念.与之对应的实体有进程.线程以及协程(coroutine).协程也叫轻量级的线程,与传统的进程和线程相比,协程的最大特点是 "轻"!可以轻松创建上 ...

  2. Golang 入门 : channel(通道)

    笔者在<Golang 入门 : 竞争条件>一文中介绍了 Golang 并发编程中需要面对的竞争条件.本文我们就介绍如何使用 Golang 提供的 channel(通道) 消除竞争条件. C ...

  3. Golang 入门 : 竞争条件

    笔者在前文<Golang 入门 : 理解并发与并行>和<Golang 入门 : goroutine(协程)>中介绍了 Golang 对并发的原生支持以及 goroutine 的 ...

  4. Golang入门(4):并发

    摘要 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求,这也是并发的必要性之一.Golang的并发控制比起Java来说,简单了不少.在Go ...

  5. Golang之chan/goroutine(转)

    原文地址:http://tchen.me/posts/2014-01-27-golang-chatroom.html?utm_source=tuicool&utm_medium=referra ...

  6. Golang 探索对Goroutine的控制方法

    前言 在golang中,只需要在函数调用前加上关键字go即可创建一个并发任务单元,而这个新建的任务会被放入队列中,等待调度器安排.相比系统的MB级别线程栈,goroutine的自定义栈只有2KB,这使 ...

  7. Java程序员的Golang入门指南(下)

    Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...

  8. Java程序员的Golang入门指南(上)

    Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如 ...

  9. 推荐一个GOLANG入门很好的网址

    推荐一个GOLANG入门很好的网址,栗子很全 https://books.studygolang.com/gobyexample/

随机推荐

  1. var、let、const声明变量的区别

    let和var声明变量的区别:1.let所声明的变量只在let命令所在的代码块内有效.(块级作用域) for(let i=0;i<10;i++){ // ... } console.log(i) ...

  2. postman使用--构建工作流和newman

    构建工作流 在使用“Collection Runner”的时候,集合中的请求执行顺序就是请求在Collection中的显示排列顺序.但是,有的时候我们不希望请求按照这样的方式去执行,可能是执行完第一个 ...

  3. tp5对接支付宝支付简单集成

    对于每个刚开始工作的新手来说,无论支付宝支付还是微信支付都是跑不掉的一个小门槛. 在加上本人比较技术比较渣(比较懒导致的),不太喜欢引用那么大的SDK,于是就简单集成了一下支付宝的支付. 但也只是只有 ...

  4. MYSQL - 限制资源的使用

    MYSQL - 限制资源的使用 1.MAX_QUERIES_PER_HOUR 用来限制用户每小时运行的查询数量 mysql> grant select on *.* to 'cu_blog'@' ...

  5. ios 注册功能研究学习

    通常,移动App的注册功能通常采用手机号码注册或者邮箱帐号注册. 不过在国内这样隐私堪忧的环境下,需要手机号来注册会流失不少用户.即便是新浪微博这样的应用,需要绑定手机号也令我不信任.除非是像淘宝.支 ...

  6. 菜鸟学习Cocos2d-x 3.x——内存管理

    菜鸟学习Cocos2d-x 3.x——内存管理 2014-12-10 分类:Cocos2d-x / 游戏开发 阅读(394) 评论(6)    亘古不变的东西 到现在,内存已经非常便宜,但是也不是可以 ...

  7. iptables(1)工具详解

    一. iptables 查看链表,创建链表,类命令 1. iptables [-t table] -N chain : 创建一条自定义规则的链 1 2     # iptables -t filter ...

  8. c++ 递归求一个数的阶乘

    #include <iostream> using namespace std; long factorial(int value); int main() { int value; co ...

  9. [学习笔记]一个实例理解Lingo的灵敏性分析

    一个实例理解Lingo的灵敏性分析     线性规划问题的三个重要概念:    最优解就是反应取得最优值的决策变量所对应的向量.    最优基就是最优单纯形表的基本变量所对应的系数矩阵如果其行列式是非 ...

  10. PAT Basic 1047

    1047 编程团体赛 编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜. 现给定所有队员的比赛成绩,请你编写程序找出冠军队. 输入格式: ...