Go :WaitGroup简介与实践
admin
2024-01-29 02:10:20

文章目录

    • 简介
    • 实践
      • 1.常见阻塞写法
      • 2.通过通道的写法
      • 3. 通过waitgroup的写法
    • 三、注意事项
      • 1. 计数器不能为负值
      • 2. WaitGroup对象不是一个引用类型
    • 小结


简介

在Go语言中,sync.WaitGroup结构体对象用于等待一组线程的结束;WaitGroup是go并发中最常用的工具,我们可以通过WaitGroup来表达这一组协程的任务是否完成,以决定是否继续往下走,或者取任务结果;

WaitGroup的结构体:

type WaitGroup struct { noCopy noCopy state1 [3]uint32 
}

在sync.WaitGroup结构体对象中有三个方法,Add()、Done()、Wait()

Add()方法主要为WaitGroup的等待数+1或者+n;

func(*WaitGroup) Add()
  • Add()方法内部计数器加上delta,delta可以是负数;
  • 如果内部计数器变为0,则Wait()方法会将处于阻塞等待的所有goroutine释放;
  • 如果计数器小于0,则调用panic()函数;
  • Add()方法加上正数的调用应在Wait()方法之前,否则Wait()方法可能只会等待很少的goroutine;
  • Add()方法在创建新的goroutine或者其它等待的事件之前调用;

Done()方法 Done函数调用的也是Add函数,主要用于-1操作;

func(wg *WaitGroup) Done()
  • Done()方法会减少WaitGroup计数器的值,一般在goroutine的最后执行;

Wait()方法 阻塞当前协程,直到等待数归为0才继续向下执行;

func (wg *WaitGroup) Wait()
  • Wait()方法会阻塞,知道WaitGroup计数器减为0.

Add()、Done()、Wait()三者对比

  • 在三个方法中,使用Add()方法添加计数,使用Done()方法减掉一个计数,如果计数不为0,则会阻塞Wait()方法的运行;
  • 一个goroutine调用Add()方法来设定等待的goroutine的数量;
  • 每个被等待的goroutine在结束时调用Done()方法;
  • 在主goroutine里可以调用Wait()方法阻塞至所有goroutine结束;

实践

1.常见阻塞写法

package mainimport ("fmt""time"
)func main(){for i := 0; i < 100 ; i++{go fmt.Println(i)}time.Sleep(time.Second)
}

主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep() 来睡眠一段时间,等待其他线程充分运行。对于简单的代码,100个for循环可以在1秒之内运行完毕,time.Sleep() 也可以达到想要的效果。但是对于实际生活的大多数场景来说,1秒是不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep() 来完成等待操作了。

2.通过通道的写法

func main() {c := make(chan bool, 100)for i := 0; i < 100; i++ {go func(i int) {fmt.Println(i)c <- true}(i)}for i := 0; i < 100; i++ {<-c}
}

首先可以肯定的是使用管道是能达到我们的目的的,而且不但能达到目的,还能十分完美的达到目的。但是管道在这里显得有些大材小用,因为它被设计出来不仅仅只是在这里用作简单的同步处理,在这里使用管道实际上是不合适的。而且假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。

3. 通过waitgroup的写法

对于这种情况,go语言中有一个其他的工具sync.WaitGroup 能更加方便的帮助我们达到这个目的。WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0。

使用WaitGroup 将上述代码可以修改为:

func main() {wg := sync.WaitGroup{}wg.Add(100)for i := 0; i < 100; i++ {go func(i int) {fmt.Println(i)wg.Done()}(i)}wg.Wait()
}

这里首先把wg 计数设置为100, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为零——也就是所有的100个for循环都运行完毕。相对于使用管道来说,WaitGroup 轻巧了许多。

三、注意事项

1. 计数器不能为负值

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

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()
}

小结

相关内容

热门资讯

江西赣州:革命老区高质量发展迈... 高铁列车从赣深高铁龙南特大桥疾驰而过,沿线田野盛开的油菜花让人心旷神怡。作为国家“八纵八横”高速铁路...
广东汕头乘风而起向未来 汕头海域的海上风电场。 (资料图片) 近日,来自近30个国家和地区的能源领域各界代表齐聚广东汕...
高价网购营养品“中招” 假货产... (来源:天津日报)转自:天津日报  家住和平区的赵先生近日遭遇烦心事:他趁电商平台促销选购并囤积的热...
埃塞俄比亚西部暴力事件已致47... 转自:央视总台记者获悉,埃塞俄比亚西部甘贝拉地区24日发生严重暴力事件,已造成至少47人死亡。为应对...
北京今天白天晴转多云,云量逐渐... 转自:北京日报客户端市气象台26日6时发布:今天白天晴转多云,北转南风二三级,最高气温2℃;夜间多云...