go|sync系列:WaitGroup、Once、Cond

sync.WaitGroup

使用方式

先来看一个例子,开启100个协程执行加法操作,开启10个协程读取加法的和。

var s int32

func main() {
   
	for i := 0; i < 100; i++ {
   
		go func() {
   
			s += 10
			time.Sleep(time.Second * 2)
		}()
	}
	for i := 0; i < 10; i++ {
   
		go func(index int) {
   
			fmt.Println("index=", index, "sum=", s)
		}(i)
	}
	fmt.Println("sum=", s)

	time.Sleep(time.Second * 5)

}
  • 在以上实示例中,有这样一段代码"time.Sleep(time.Second*5)"是为了防止主函数main提前返回,其余goroutine来不及执行。但是有这样一个问题,我们无法确定100个执行加法的协程和执行10个读取的协程什么时候会执行完成,如果设置的等待时间太长会导致主协程等待的时间太长,如果设置的等待的时间太短,会导致任务还没有执行完成就提前退出。
  • 为了解决上述的问题,引入了waitgroup。主要的作用就是用来等待其余协程全部执行完成之后再推出。

为什么主协程执行完后就会退出,不会等待子协程
可以参考视频:“Hello Goroutine的执行过程

上述的例子用waitgroup进行改写

func main() {
   
	wg := sync.WaitGroup{
   }
	wg.Add(110)
	//100个goroutine用来执行写操作
	for i := 0; i < 100; i++ {
   
		go func() {
   
			defer wg.Done()
			s += 10
			time.Sleep(time.Second * 2)
		}()
	}
	//10个goroutine执行读操作
	for i := 0; i < 10; i++ {
   
		go func(index int) {
   
			defer wg.Done()
			fmt.Println("index=", index, "sum=", s)
		}(i)
	}
	fmt.Println("sum=", s)

	wg.Wait()
}
  • 声明一个sync.WaitGroup,然后通过Add方法设置计数器的值,需要等待多少数量的协程就设置成多少。

  • 在每个协程执行完毕时调用Done方法,让计数器减1,告诉Wait该协程已经执行完毕。

  • 最后调用Wait方法一直等待,直到计数器的值为0,也即是所有的协程都执行完毕。此时主协程可以退出。

    sync.WaitGroup适合协调多个协程共同做一件事的场景,比如下载一个文件,假设使用10个协程,每个协程下载文件的1/10大小,只有10个协程都下载好了整个文件才算下载好。

底层原理

WaitGroup是一个结构体,先来看一下它的定义

/*
WaitGroup可以等待一些数量的协程完成相关的逻辑操作
主协程调用"Add"方法增加需要等待的正在运行的协程的数量;
每一个协程执行完自己的逻辑之后调用"Done"方法将需要等待的协程数量减少。与此同时"Wait"方法会阻塞直到需要等待的协程全部退出。
waitGroup在第一次使用之后禁止复制
在Wait阻塞之前Done的调用是并发安全的
*/
type WaitGroup struct {
   
//type noCopy struct{}
	noCopy noCopy 
	/*
	高32bit充当计数器的功能,记录需要追踪的协程数量。
	低32bit记录的是,因为wait操作而被阻塞的协程数量。
	*/
	state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
	sema  uint32//用来同步Add中的读操作和Wait中的写操作
}

看"Add\Done\Wait"方法之前先看一下,有关信号量的两个操作

/*
Semacquire等待直到s中存储的数值大于0,然后自动减少它
它是一个简单的睡眠原语
*/
func runtime_Semacquire(s *uint32)
/*
Semrelease自动增加s中存储的数值,并通知一个等待该信号量的协程;
是一个简单的唤醒原语
handoff为true,直接把count传递给第一个waiter
skipframes是跟踪过程中要忽略的帧数,从runtime_Semrelease调用者开始计数。
*/
func runtime_Semrelease(s *uint32, handoff bool, skipframes int)

Add

先来看一看"Add"方法

/*
Add方法向WaitGroup的计数器添加delta,有可能变为负值;
如果计数器变为0,所有被Wait阻塞的协程都将被释放
如果计数器变为负数,则会产生panic

*/
func (wg *WaitGroup) Add(delta
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值