Go语言WaitGroup使用时需要注意的坑

 更新时间:2016年12月06日 09:58:24   投稿:daisy  
Go语言中WaitGroup的用途是它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。之前一直使用也没有问题,但最近通过同事的一段代码引起了关于WaitGroup的注意,下面这篇文章就介绍了WaitGroup使用时需要注意的坑及填坑。

前言

WaitGroup在go语言中,用于线程同步,单从字面意思理解,wait等待的意思,group组、团队的意思,WaitGroup就是指等待一组,等待一个系列执行完成后才会继续向下执行。Golang 中的 WaitGroup 一直是同步 goroutine 的推荐实践。自己用了两年多也没遇到过什么问题。

直到最近的一天同事扔过来一段奇怪的代码:

第一个坑

复制代码 代码如下:

package main
 
import (
    "log"
 
    "sync"
)
 
func main() {
    wg := sync.WaitGroup{}
 
    for i := 0; i < 5; i++ {
        go func(wg sync.WaitGroup, i int) {
            wg.Add(1)
            log.Printf("i:%d", i)
            wg.Done()
        }(wg, i)
    }
 
    wg.Wait()
 
    log.Println("exit")
}

撇了一眼,觉得没什么问题。

然而,它的运行结果是这样:

复制代码 代码如下:

2016/11/27 15:12:36 exit
[Finished in 0.7s]

或这样:

复制代码 代码如下:

2016/11/27 15:21:51 i:2
2016/11/27 15:21:51 exit
[Finished in 0.8s]

或这样:

复制代码 代码如下:

2016/11/27 15:22:51 i:3
2016/11/27 15:22:51 i:2
2016/11/27 15:22:51 exit
[Finished in 0.8s]

一度让我以为手上的 mac 也没睡醒……

这个问题如果理解了 WaitGroup 的设计目的就非常容易 fix 啦。因为 WaitGroup 同步的是 goroutine, 而上面的代码却在 goroutine 中进行 Add(1) 操作。因此,可能在这些 goroutine 还没来得及 Add(1) 已经执行 Wait 操作了。

于是代码改成了这样:

第二个坑

复制代码 代码如下:

package main
 
import (
    "log"
 
    "sync"
)
 
func main() {
    wg := sync.WaitGroup{}
 
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(wg sync.WaitGroup, i int) {
            log.Printf("i:%d", i)
            wg.Done()
        }(wg, i)
    }
 
    wg.Wait()
 
    log.Println("exit")
}

然而,mac 又睡了过去,而且是睡死了过去:

复制代码 代码如下:

2016/11/27 15:25:16 i:1
2016/11/27 15:25:16 i:2
2016/11/27 15:25:16 i:4
2016/11/27 15:25:16 i:0
2016/11/27 15:25:16 i:3
fatal error: all goroutines are asleep - deadlock!

wg 给拷贝传递到了 goroutine 中,导致只有 Add 操作,其实 Done操作是在 wg 的副本执行的。因此 Wait 就死锁了。

于是代码改成了这样:

填坑

复制代码 代码如下:

package main
 
import (
    "log"
 
    "sync"
)
 
func main() {
    wg := &sync.WaitGroup{}
 
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(wg *sync.WaitGroup, i int) {
            log.Printf("i:%d", i)
            wg.Done()
        }(wg, i)
    }
 
    wg.Wait()
 
    log.Println("exit")
}
 

总结

好了,到这里终于解决了,以上就是关于Go语言WaitGroup使用时需要注意的一些坑,希望本文中提到的这些问题对大家学习或者使用Go语言的时候能有所帮助,如果有疑问大家可以留言交流。

相关文章

  • Golang中Gin数据库表名前缀的三种方法

    Golang中Gin数据库表名前缀的三种方法

    本文主要介绍了Golang中Gin数据库表名前缀的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • 浅谈Golang Slice切片如何扩容的实现

    浅谈Golang Slice切片如何扩容的实现

    本文主要介绍了浅谈Golang Slice切片如何扩容的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Go语言中嵌入式缓存库的用法详解

    Go语言中嵌入式缓存库的用法详解

    Go 语言中有一些非常高效的嵌入式缓存库,groupcache 和 bigcache 是两个非常流行且高性能的库,本文将详细介绍一下二者的用法,有需要的小伙伴可以参考下
    2025-01-01
  • Go语言kube-scheduler深度剖析与开发之pod调度

    Go语言kube-scheduler深度剖析与开发之pod调度

    这篇文章主要为大家介绍了Go语言kube-scheduler深度剖析与开发,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • 使用golang实现在屏幕上打印进度条的操作

    使用golang实现在屏幕上打印进度条的操作

    这篇文章主要介绍了使用golang实现在屏幕上打印进度条的操作,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • golang 熔断限流降级实践

    golang 熔断限流降级实践

    本文主要介绍了golang 熔断限流降级实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • go-zero创建RESTful API 服务的方法

    go-zero创建RESTful API 服务的方法

    文章介绍了如何使用go-zero框架和goctl工具快速创建RESTfulAPI服务,通过定义.api文件并使用goctl命令,可以自动生成项目结构、路由、请求和响应模型以及处理逻辑,感兴趣的朋友一起看看吧
    2024-11-11
  • Go iota关键字与枚举类型实现原理

    Go iota关键字与枚举类型实现原理

    这篇文章主要介绍了Go iota关键字与枚举类型实现原理,iota是go语言的常量计数器,只能在常量的表达式中使用,更多相关内容需要的小伙伴可以参考一下
    2022-07-07
  • go实现fping功能

    go实现fping功能

    这篇文章主要介绍了go实现fping功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • 使用Go语言开发一个高并发系统

    使用Go语言开发一个高并发系统

    高并发系统是指能同时支持众多用户请求,处理大量并行计算的系统,这篇文章主要为大家详细介绍了如何使用Go语言开发一个高并发系统,感兴趣的小伙伴可以了解下
    2023-11-11

最新评论