目录

  • WaitGroup介绍
  • WaitGroup的实现

    • Add
    • Done
    • Wait

WaitGroup介绍

waitGroup ,也是在go语言并发中比拟罕用的语法,所以在这里咱们一起分析 waitGroup 的应用形式及其源码解读。

WaitGroup 也是sync 包下一份子,用来解决工作编排的一个并发原语。它次要解决了并发-期待问题:比方当初有三个goroutine,别离为goroutineAgoroutineBgoroutineC,而goroutineA须要期待goroutineBgoroutineC这一组goroutine全副执行结束后,才能够执行后续业务逻辑。此时就能够应用 WaitGroup 轻松解决。

在这个场景中,goroutineA为主goroutine,goroutineBgoroutineC为子goroutine。goroutineA则须要在检查点(checkout point) 期待goroutineBgoroutineC全副执行结束,如果在执行工作的goroutine还没全副实现,那么goroutineA就会阻塞在检查点,直到所有goroutine都实现后能力继续执行。

代码实现:

package mainimport (  "fmt"  "sync")func goroutineB(wg *sync.WaitGroup) {  defer wg.Done()  fmt.Println("goroutineB Execute")  time.Sleep(time.Second)}func goroutineC(wg *sync.WaitGroup) {  defer wg.Done()  fmt.Println("goroutineC Execute")  time.Sleep(time.Second)}func main() {  var wg sync.WaitGroup  wg.Add(2)  go goroutineB(&wg)  go goroutineC(&wg)  wg.Wait()  fmt.Println("goroutineB and goroutineC finished...")}

运行后果:

goroutineC ExecutegoroutineB ExecutegoroutineB and goroutineC finished...

上述就是WaitGroup 的简略操作,它的语法也是比较简单,提供了三个办法,如下所示:

func (wg *WaitGroup) Add(delta int)func (wg *WaitGroup) Done()func (wg *WaitGroup) Wait()
  • Add:用来设置WaitGroup的计数值(子goroutine的数量)
  • Done:用来将WaitGroup的计数值减1,起始就是调用Add(-1)
  • Wait:调用这个办法的goroutine会始终阻塞,直到WaitGroup的技术值变为0

接下来,咱们进行分析 WaitGroup 的源码实现,让其无处可遁,它源码比拟少,除去正文,也就几十行,对老手来说也是一种不错的抉择。

WaitGroup的实现

首先,咱们看看 WaitGroup 的数据结构,它包含了一个noCopy 的辅助字段,一个具备复合意义的state1字段。

  • noCopy 的辅助字段:次要就是辅助 vet 工具查看是否通过 copy 赋值这个 WaitGroup 实例。我会在前面和你详细分析这个字段
  • state1:具备复合意义的字段,蕴含WaitGroup计数值,阻塞在检查点的主gooutine和信号量
type WaitGroup struct {    // 防止复制应用的一个技巧,能够通知vet工具违反了复制应用的规定    noCopy noCopy    // 64bit(8bytes)的值分成两段,高32bit是计数值,低32bit是waiter的计数    // 另外32bit是用作信号量的    // 因为64bit值的原子操作须要64bit对齐,然而32bit编译器不反对,所以数组中的元素在不同的架构中不一样,具体解决看上面的办法    // 总之,会找到对齐的那64bit作为state,其余的32bit做信号量    state1 [3]uint32}// 失去state的地址和信号量的地址func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {    if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {        // 如果地址是64bit对齐的,数组前两个元素做state,后一个元素做信号量        return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]    } else {        // 如果地址是32bit对齐的,数组后两个元素用来做state,它能够用来做64bit的原子操作,第一个元素32bit用来做信号量        return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]    }}    

因为对 64 位整数的原子操作要求整数的地址是 64 位对齐的,所以针对 64 位和 32 位环境的 state 字段的组成是不一样的。

在 64 位环境下,state1 的第一个元素是 waiter 数,第二个元素是 WaitGroup 的计数值,第三个元素是信号量。

在 32 位环境下,如果 state1 不是 64 位对齐的地址,那么 state1 的第一个元素是信号量,后两个元素别离是 waiter 数和计数值。

接下里,咱们一一看 Add 办法、 Done 办法、 Wait 办法的实现原理。

Add

Add办法实现思路:

Add办法次要操作的state1字段中计数值局部。当Add办法被调用时,首先会将delta参数值左移32位(计数值在高32位),而后外部通过原子操作将这个值加到计数值上。须要留神的是,delta的取值范畴可正可负,因为调用Done()办法时,外部通过Add(-1)办法实现的。

代码实现如下:

func (wg *WaitGroup) Add(delta int) {  // statep示意wait数和计数值  // 低32位示意wait数,高32位示意计数值   statep, semap := wg.state()   // uint64(delta)<<32 将delta左移32位    // 因为高32位示意计数值,所以将delta左移32,减少到技术上   state := atomic.AddUint64(statep, uint64(delta)<<32)   // 以后计数值   v := int32(state >> 32)   // 阻塞在检查点的wait数   w := uint32(state)   if v > 0 || w == 0 {      return   }      // 如果计数值v为0并且waiter的数量w不为0,那么state的值就是waiter的数量    // 将waiter的数量设置为0,因为计数值v也是0,所以它们俩的组合*statep间接设置为0即可。此时须要并唤醒所有的waiter   *statep = 0   for ; w != 0; w-- {      runtime_Semrelease(semap, false, 0)   }}

Done

外部就是调用Add(-1)办法,这里就不细讲了。

// Done办法理论就是计数器减1func (wg *WaitGroup) Done() {   wg.Add(-1)}

Wait

wait实现思路:

一直查看state值。如果其中的计数值为零,则阐明所有的子goroutine已全副执行结束,调用者不用期待,间接返回。如果计数值大于零,阐明此时还有工作没有实现,那么调用者变成期待者,须要退出wait队列,并且阻塞本人。

代码实现如下:

func (wg *WaitGroup) Wait() {   // statep示意wait数和计数值   // 低32位示意wait数,高32位示意计数值   statep, semap := wg.state()   for {      state := atomic.LoadUint64(statep)      // 将state右移32位,示意以后计数值      v := int32(state >> 32)      // w示意waiter期待值      w := uint32(state)      if v == 0 {         // 如果以后计数值为零,示意以后子goroutine已全副执行结束,则间接返回         return      }      // 否则应用原子操作将state值加一。      if atomic.CompareAndSwapUint64(statep, state, state+1) {         // 阻塞休眠期待         runtime_Semacquire(semap)         // 被唤醒,不再阻塞,返回         return      }   }}

到此,waitGroup的根本应用和实现原理已介绍结束了,置信大家已有不一样的播种,咱们下期见。

文章也会继续更新,能够微信搜寻「 迈莫coding 」第一工夫浏览。每天分享优质文章、大厂教训、大厂面经,助力面试,是每个程序员值得关注的平台。