极客时间--golang并发编程实战课--WaitGroup学习总结

发布时间 2023-06-04 00:25:17作者: 99号的格调

什么是WaitGroup?

WaitGroup解决的就是并发-等待问题:现在有一个goroutineA在检查点等待一组goroutine全部完成,如果

在执行任务的这些goroutine还没全部完成,那么goroutine A就会阻塞在检查点,知道所有goroutine都完成后才能继续执行。

Golang的标准库的WaitGroup提供了三个方法:

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg &WaitGroup) Wait()

Add :用来设置waitgroup的计数值

Done:用来将waitgroup的计数值减1,其实就是调用了Add(-1)

Wait:调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数器的计数值为0

代码示例:启动10个woker,分别对计数值+1,10个worker都完成后,我们希望打印出计数器的值

package main

import (
	"fmt"
	"sync"
	"time"
)
//一个线程安全的计数器
type Counter struct {
	mu    sync.Mutex
	count uint64
}
//对计数器的值➕1
func (c *Counter) Incr() {
	c.mu.Lock()
	c.count++
	c.mu.Unlock()
}
//获取当前的计数器的值
func (c *Counter) Count() uint64 {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.count
}
//sleep+1,然后计数+1
func Worker(c *Counter, wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(time.Second)
	c.Incr()
}

func main() {
	var count Counter
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go Worker(&count, &wg)
	}
	wg.Wait()
	fmt.Println(count.Count())
}

WaitGroup的实现

数据结构如下:

type WaitGroup struct {
	noCopy noCopy//避免复制使用的一个技巧,可以告诉vet工具违反了复制使用的规则
	state1 uint64
	state2 uint32
}

Add方法的逻辑:主要操作的是state的计数部分,可以为计数值增加一个delta值,内部通过原子操作把这个值加到计数器上,需要注意的是,这个delta值可以为负数,Done方法内部其实就是通过Add(-1)实现的。

Wait方法的逻辑:不断检查delta值,如果其中的计数值==0,那么说明所有的任务已完成,调用者不必等待,直接返回,如果计数值大于0,说明此时还有任务没完成,那么调用者就变成了等待者,需要加入waiter队列,并且阻塞住自己。

使用总结:

保证所有的Add方法调用都在wait之前

不传递负数给Add方法,只通过Done来给计数值-1

不做多余的Done方法调用,保证Add的计数值和Done方法调用的数量是一致的

不遗漏Done方法的调用,否则会导致panic

附来自课程上的一幅插图: