• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Go并发:使用sync.WaitGroup实现协程同步方式

    经常看到有人会问如何等待主协程中创建的协程执行完毕之后再结束主协程,例如如下代码:

    package main
    import (
        "fmt"
    )
    func main() {
        go func() {
            fmt.Println("Goroutine 1")
        }()
        go func() {
            fmt.Println("Goroutine 2")
        }()
    }
    

    执行以上代码很可能看不到输出,因为有可能这两个协程还没得到执行主协程已经结束了,而主协程结束时会结束所有其他协程。

    解决办法是可以在main函数结尾加上等待:

    package main
    import (
        "fmt"
        "time"
    )
    func main() {
        go func() {
            fmt.Println("Goroutine 1")
        }()
        go func() {
            fmt.Println("Goroutine 2")
        }()
        time.Sleep(time.Second * 1) // 睡眠1秒,等待上面两个协程结束
    }
    

    这并不是完美的解决方法,如果这两个协程中包含复杂的操作,可能很耗时间,就无法确定需要睡眠多久,当然可以用管道实现同步:

    package main
    import (
        "fmt"
    )
    func main() {
        ch := make(chan struct{})
        count := 2 // count 表示活动的协程个数
        go func() {
            fmt.Println("Goroutine 1")
            ch - struct{}{} // 协程结束,发出信号
        }()
        go func() {
            fmt.Println("Goroutine 2")
            ch - struct{}{} // 协程结束,发出信号
        }()
        for range ch {
            // 每次从ch中接收数据,表明一个活动的协程结束
            count--
            // 当所有活动的协程都结束时,关闭管道
            if count == 0 {
                close(ch)
            }
        }
    }
    

    上面的解决方案是比较完美的方案,但是Go提供了更简单的方法——使用sync.WaitGroup。

    WaitGroup顾名思义,就是用来等待一组操作完成的。

    WaitGroup内部实现了一个计数器,用来记录未完成的操作个数,它提供了三个方法,Add()用来添加计数。

    Done()用来在操作结束时调用,使计数减一。

    Wait()用来等待所有的操作结束,即计数变为0,该函数会在计数不为0时等待,在计数为0时立即返回。

    package main
    import (
        "fmt"
        "sync"
    )
    func main() {
        var wg sync.WaitGroup
        wg.Add(2) // 因为有两个动作,所以增加2个计数
        go func() {
            fmt.Println("Goroutine 1")
            wg.Done() // 操作完成,减少一个计数
        }()
        go func() {
            fmt.Println("Goroutine 2")
            wg.Done() // 操作完成,减少一个计数
        }()
        wg.Wait() // 等待,直到计数为0
    }
    

    可见用sync.WaitGroup是最简单的方式。

    补充:Golang 中使用WaitGroup的那点坑

    sync.WaitGroup对于Golang开发者来说并不陌生,其经常作为多协程之间同步的一种机制。用好它势必会让你事半功倍,但是一旦错用将引发问题。

    关于WaitGroup的使用网上有很多例子,在此就不做介绍了,我想说的是我在项目中使用WaitGroup遇到的坑。

    在项目中,因为服务器有同步需求, 所以直接使用了WaitGroup,但是未考虑使用场景,结果在项目上线之后,高峰期的时候客户端经常出现卡顿,经过多方查找,才发现如果使用WaitGroup的时候,未启动单独的goroutine,那么极有可能造成主线程的阻塞,

    所以我做了下面的测试(测试中,我把WaitGroup置于协程内):

    import (
     "fmt"
     "sync"
     "time"
    )
     
    func main() {
        fmt.Println("main-1")
     testW()
     fmt.Println("main-2")
     time.Sleep(time.Duration(15) * time.Second) 
    }
     
    func testW() {
     fmt.Println("testW-1")
     go func() {
      var wg sync.WaitGroup
      fmt.Println("testW-2")
      testW1(wg)
      fmt.Println("testW-5")
      wg.Wait()
      fmt.Println("testW-6")
     }()
    }
     
    func testW1(wg *sync.WaitGroup) {
     wg.Add(1)
     fmt.Println("testW-3")
     time.AfterFunc(time.Second*5, func() {
      wg.Done()
     })
     fmt.Println("testW-4") 
    }

    输出为:

    main-1

    testchan-1

    main-2

    testchan-2

    testchan-3

    testchan-4

    testchan-5

    // 过5秒

    testchan-6

    总结:

    将WaitGroup用于goroutine内,不会导致主线程的阻塞,同样可以实现同步的效果。

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

    您可能感兴趣的文章:
    • Golang 实现分片读取http超大文件流和并发控制
    • Go 并发控制context实现原理剖析(小结)
    • 解决Golang 中使用WaitGroup的那点坑
    • Go语言WaitGroup使用时需要注意的坑
    • Golang中的sync.WaitGroup用法实例
    • Go并发控制WaitGroup的使用场景分析
    上一篇:go等待一组协程结束的操作方式
    下一篇:Go 如何批量修改文件名
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    Go并发:使用sync.WaitGroup实现协程同步方式 并发,使用,sync.WaitGroup,实现,