美文网首页互联网科技golang进阶之路Golang 入门资料+笔记
Golang并发利用sync.WaitGroup实现协程同步

Golang并发利用sync.WaitGroup实现协程同步

作者: 不屈真实 | 来源:发表于2020-06-19 18:24 被阅读0次

协程同步

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

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 100 ; i++{
        go func(i int) {
            fmt.Println("Goroutine ",i)
        }(i)
    }
}

执行以上代码很可能看不到输出也可能只执行了部分协程,因为有可能这两个协程还没得到执行主协程已经结束了,而主协程结束时会结束所有其他协程。解决办法是可以在main函数结尾加上sleep()等待:

package main

import (
    "fmt"
    "time"
)

func main() {
  for i := 0; i < 100 ; i++{
        go func(i int) {
            fmt.Println("Goroutine ",i)
        }(i)
    }
    time.Sleep(time.Second * 1) // 睡眠1秒,等待上面两个协程结束
}

主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep() 来睡眠一段时间,等待其他线程充分运行。对于简单的代码,多个for循环可以在1秒之内运行完毕,time.Sleep() 也可以达到想要的效果。
但是对于实际生活的大多数场景来说,1秒是不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep() 来完成等待操作了。这并不是完美的解决方法,如果这两个协程中包含复杂的操作,可能很耗时间,就无法确定需要睡眠多久,当然可以用管道实现同步:

package main

import (
    "fmt"
)

func main() {

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

上面的解决方案是比较完美的方案,首先可以肯定的是使用管道是能达到我们的目的的,而且不但能达到目的,还能十分完美的达到目的。但是管道在这里显得有些大材小用,因为它被设计出来不仅仅只是在这里用作简单的同步处理,在这里使用管道实际上是不合适的。而且假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。Go提供了更简单的方法——使用sync.WaitGroup。WaitGroup顾名思义,就是用来等待一组操作完成的。WaitGroup内部实现了一个计数器,用来记录未完成的操作个数,它提供了三个方法,Add()用来添加计数。Done()用来在操作结束时调用,使计数减一。Wait()用来等待所有的操作结束,即计数变为0,该函数会在计数不为0时等待,在计数为0时立即返回。

package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup

    wg.Add(100) // 因为有两个动作,所以增加2个计数
     for i := 0; i < 100 ; i++{
        go func(i int) {
            fmt.Println("Goroutine ",i)
                       wg.Done() // 操作完成,减少一个计数
        }(i)
    }

    wg.Wait() // 等待,直到计数为0
}

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

注意事项

1. 计数器不能为负值
我们不能使用Add() 给wg 设置一个负值,否则代码将会报错:

panic: sync: negative WaitGroup counter
 
goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
    D:/Go/src/sync/waitgroup.go:75 +0x1d0
main.main()
    D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54

同样使用Done() 也要特别注意不要把计数器设置成负数了。

2. WaitGroup对象不是一个引用类型
WaitGroup对象不是一个引用类型,在通过函数传值的时候需要使用地址:

func main() {
    wg := sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go f(i, &wg)
    }
    wg.Wait()
}
 
// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup) { 
    fmt.Println(i)
    wg.Done()
}

相互学习。共同进步。

相关文章

  • Golang并发利用sync.WaitGroup实现协程同步

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

  • Golang的并发编程

    Golang中的并发编程 Golang语言中,并发使用的协程的机制,实现起来也是十分的方便,使用go关键字即可。 ...

  • 协程技术

    1、协程(Coroutine):比线程的单位更小,在一个线程中可以开启多个协程,利用多个协程实现并发。 2、协程跟...

  • Golang并发、协程

    并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。 并发(concurrency):指在同...

  • Goroutine和Channel总结

    Golang对并发的支持相对简单,采用的是Goroutine的机制,类似于协程。 采用Go关键字就能实现。 Gor...

  • Python实现协程实例

    1. 目标:在线程下实现并发并发(多个任务看起来是同时执行就是并发):切换+保存状态 2. 协程:协程是单线程实现...

  • 【Golang】通道channel

    Java的并发:基于线程Golang的并发:基于协程goroutine 并发会导致资源竞争:加锁防止资源竞争的三种...

  • Channel简易教程

    不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程...

  • Golang中超简单的协程同步工具

    Golang中超简单的协程同步工具 从需求出发 现在有如下需求:多协程处理一批数据,但需要所有协程都跑完才可以继续...

  • go的协程并发-channel消息机制

    go的协程并发-channel消息机制 方式一 方式二 go-协程实现方案汇总

网友评论

    本文标题:Golang并发利用sync.WaitGroup实现协程同步

    本文链接:https://www.haomeiwen.com/subject/yfdbxktx.html