Golang 入门 : 等待 goroutine 完成任务
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 执行完成。
使用方法可以总结为下面几点:
- 创建一个 WaitGroup 实例,比如名称为:wg
- 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
- 在每个 goroutine 运行的函数中执行 defer wg.Done()
- 调用 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 完成任务的更多相关文章
- Golang 入门 : goroutine(协程)
在操作系统中,执行体是个抽象的概念.与之对应的实体有进程.线程以及协程(coroutine).协程也叫轻量级的线程,与传统的进程和线程相比,协程的最大特点是 "轻"!可以轻松创建上 ...
- Golang 入门 : channel(通道)
笔者在<Golang 入门 : 竞争条件>一文中介绍了 Golang 并发编程中需要面对的竞争条件.本文我们就介绍如何使用 Golang 提供的 channel(通道) 消除竞争条件. C ...
- Golang 入门 : 竞争条件
笔者在前文<Golang 入门 : 理解并发与并行>和<Golang 入门 : goroutine(协程)>中介绍了 Golang 对并发的原生支持以及 goroutine 的 ...
- Golang入门(4):并发
摘要 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求,这也是并发的必要性之一.Golang的并发控制比起Java来说,简单了不少.在Go ...
- Golang之chan/goroutine(转)
原文地址:http://tchen.me/posts/2014-01-27-golang-chatroom.html?utm_source=tuicool&utm_medium=referra ...
- Golang 探索对Goroutine的控制方法
前言 在golang中,只需要在函数调用前加上关键字go即可创建一个并发任务单元,而这个新建的任务会被放入队列中,等待调度器安排.相比系统的MB级别线程栈,goroutine的自定义栈只有2KB,这使 ...
- Java程序员的Golang入门指南(下)
Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...
- Java程序员的Golang入门指南(上)
Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如 ...
- 推荐一个GOLANG入门很好的网址
推荐一个GOLANG入门很好的网址,栗子很全 https://books.studygolang.com/gobyexample/
随机推荐
- Apache Commons Configuration的应用
Apache Commons Configuration的应用 Commons Configuration是一个java应用程序的配置管理工具.可以从properties或者xml文件中加载软件的配置 ...
- java在线聊天项目 swt可视化窗口Design 登录框注册按钮点击改变窗口大小——出现注册面板 实现打开登录框时屏幕居中
登录框注册按钮点击改变窗口大小——出现注册面板 首先用swt可视化设计登录窗口如下图: 此时窗口高度为578 没点击注册时高度为301(可自己定) 注意:注册用户的Jpanel 的border选择T ...
- react 列表渲染
https://reactjs.org/docs/lists-and-keys.html#keys 以下代码运行会报错:Warning: Each child in an array or itera ...
- Kali入门配置使用(一)
一.Kali简介 1.1.相关连接 Kali百度百科:https://baike.baidu.com/item/Kali%20linux/8305689?fr=aladdin Kali wiki:ht ...
- 有关Kali处理源的方法
sudo apt-get update 更新源sudo apt-get install package 安装包sudo apt-get remove package 删除包sudo apt-cach ...
- (原)剑指offer之位运算
题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 思路: count为1的位数,初始为零 每次右移一为,与1做与运算,结果不为零说明最后一位为1 c++代码如下 in ...
- 关于网络IP地址的分类
一.IP地址的分类 众所周知,IP地址都是以点号.分为4段来表示.不同类的IP前几位的表示含义也不尽相同. 1.A类IP [网络地址] 第一位表示网络地址,且第一个字节的第一位必须以0开头.依据此原则 ...
- cf950f Curfew
神贪心--写了一个晚上加一个早上. 先考虑只有一个宿管的情况. 首先,如果这个宿舍人多了,多余的人就跑到下一个宿舍.(如果这是最后一个宿舍的话,多的就躺床底下) 如果这个宿舍人少了,但是能从别的宿舍调 ...
- luogu2051 [AHOI2009]中国象棋
巨水,调了好久,心态爆炸 #include <iostream> #include <cstring> #include <cstdio> using namesp ...
- ci $this->load->database()
http://pengbotao.cn/codeigniter-database.html