Go并发控制-WaitGroup简用

在Go语言,实现同步的一种方式就是WaitGroup。

package main

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

 主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep() 来睡眠一段时间(如果不加此,你将看不到全部打印的1000个数字,因主程序退出,并不等待go协程结束),等待其他线程充分运行。对于简单的代码,1000个for循环可以在1秒之内运行完毕,time.Sleep() 也可以达到想要的效果。

 但是对于实际生活的大多数场景来说,1秒或多或少,够或不够,都会造成事故,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep() 来完成等待操作了。

可以考虑使用管道来完成上述操作:

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

  首先可以肯定的是使用管道是能十分完美的达到目的。
但是管道在这里显得有些大材小用,因为它被设计出来不仅仅只是在这里用作简单的同步处理,在这里使用管道实际上是不合适的。而且假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。对于这种情况,go语言中有一个其他的工具sync.WaitGroup 能更加方便的帮助我们达到这个目的。WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0。

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

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

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

package main

import (
	"fmt"
	"sync"
)
func test(n int){
	defer wg.Done()  //协程执行完毕,通知Add函数减1
	fmt.Println(n) 
}

var wg sync.WaitGroup

func main(){
   for i := 0; i < 1000; i++ {
	wg.Add(1)
	go test(i)
  }
 wg.Wait()    //检查Add函数是否为0,为0,主线程结束,不为0,,main函数主协程不退出,一直阻塞下去,一直等待子协程执行完毕
}
posted @ 2021-10-30 17:28  柠檬橘  阅读(126)  评论(0编辑  收藏  举报