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

 更新时间:2021年05月04日 09:19:09   作者:Cynhard85  
这篇文章主要介绍了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中基础的命令行模块urfave/cli的用法说明

    Golang中基础的命令行模块urfave/cli的用法说明

    这篇文章主要介绍了Golang中基础的命令行模块urfave/cli的用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • golang 生成对应的数据表struct定义操作

    golang 生成对应的数据表struct定义操作

    这篇文章主要介绍了golang 生成对应的数据表struct定义操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Golang中由零值和gob库特性引起BUG解析

    Golang中由零值和gob库特性引起BUG解析

    这篇文章主要为大家介绍了Golang中由零值和gob库特性引起BUG解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Go语言字符串操作指南:简单易懂的实战技巧

    Go语言字符串操作指南:简单易懂的实战技巧

    本文将介绍Go语言中字符串的实战操作,通过本文的学习,读者将掌握Go语言中字符串的常用操作,为实际开发提供帮助,需要的朋友可以参考下
    2023-10-10
  • 用golang实现一个定时器任务队列实例

    用golang实现一个定时器任务队列实例

    golang中提供了2种定时器timer和ticker,分别是一次性定时器和重复任务定时器。这篇文章主要介绍了用golang实现一个定时器任务队列实例,非常具有实用价值,需要的朋友可以参考下
    2018-05-05
  • Go语言实现百万级WebSocket连接架构设计及服务优化

    Go语言实现百万级WebSocket连接架构设计及服务优化

    本文将详细介绍如何在Go中构建一个能够支持百万级WebSocket连接的服务,包括系统架构设计、性能优化策略以及具体的实现步骤和代码示例
    2024-01-01
  • golang panic及处理机制

    golang panic及处理机制

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱,今天给大家介绍golang panic及处理机制,需要的朋友参考下吧
    2021-08-08
  • 深入理解Go语言设计模式之函数式选项模式

    深入理解Go语言设计模式之函数式选项模式

    在 Go 语言中,函数选项模式(Function Options Pattern)是一种常见且强大的设计模式,用于构建可扩展、易于使用和灵活的 API,本文就来看看它的具体用法吧
    2023-05-05
  • go语言里包的用法实例

    go语言里包的用法实例

    这篇文章主要介绍了go语言里包的用法,实例分析了Go语言里包的原理与使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • 快速解决Golang Map 并发读写安全的问题

    快速解决Golang Map 并发读写安全的问题

    这篇文章主要介绍了快速解决Golang Map 并发读写安全的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12

最新评论