go WaitGroup源码分析

适用场景

等待若干个任务执行完成。

实现原理

字段

type WaitGroup struct { 
    省略
    
    // 存储waiter数、WaitGroup计数和信号量
    state1 [3]uint32
}

添加任务Add函数

设置WaitGroup计数

func (wg *WaitGroup) Add(delta int) { 
    statep, semap := wg.state()
    // 通过原子操作来增加WaitGroup计数,高32位是WaitGroup计数
    state := atomic.AddUint64(statep, uint64(delta)<<32)
    // 获取当前WaitGroup计数
    v := int32(state >> 32)
    // 获取当前waiter数
    w := uint32(state)
 
    // 如果还有等待任务或者等待者数量是0,那么不需要唤醒等待者
    if v > 0 || w == 0 { 
        return 
    } 
 
    // WaitGroup计数是0,waiter数设为0
    *statep = 0
    // 唤醒所有的waiter
    for ; w != 0; w-- {
        runtime_Semrelease(semap, false, 0)
    }
}

任务结束Done函数

WaitGroup计数减1

func (wg *WaitGroup) Done() { 
    wg.Add(-1) 
}

等待任务结束Wait函数

如果WaitGroup计数是0,那么调用者不等待,直接返回;如果WaitGroup计数大于0,那么调用者加入waiter队列,阻塞自己。

func (wg *WaitGroup) Wait() { 
    statep, semap := wg.state() 
     
    for { 
        state := atomic.LoadUint64(statep) 
        // 获取当前WaitGroup计数
        v := int32(state >> 32)
        // 获取当前waiter数
        w := uint32(state)
        if v == 0 { 
            // 如果WaitGroup计数为0,那么调用这个方法的goroutine不再等待 
            return 
        } 
        // waiter数加1,cas失败后通过外层for循环来继续尝试cas 
        if atomic.CompareAndSwapUint64(statep, state, state+1) { 
            // 阻塞等待 
            runtime_Semacquire(semap) 
            // 被唤醒,不再阻塞,返回 
            return 
        } 
    } 
}

 

posted on 2023-01-21 16:20  王景迁  阅读(18)  评论(0编辑  收藏  举报

导航