Go WaitGroup 源码分析

大二小的宝 · · 440 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

概述

go语言sync库中的WaitGroup是用于等待一个协程或者一组携程。使用Add函数增加计数器,使用Done函数减少计数器。当使用Wait函数等待计数器归零之后则唤醒主携程。需要注意的是:

  • Add和Done函数一定要配对,否则可能发生死锁
  • WaitGroup结构体不能复制

源码分析

WaitGroup 对象

type WaitGroup struct {
    noCopy noCopy
    // 位值:高32位是计数器,低32位是goroution等待计数。
    state1 [12]byte
    // 信号量,用于唤醒goroution
    sema   uint32
}

func (wg *WaitGroup) state() *uint64 {
    if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
        return (*uint64)(unsafe.Pointer(&wg.state1))
    } else {
        return (*uint64)(unsafe.Pointer(&wg.state1[4]))
    }
}

Add,Done,Wait

func (wg *WaitGroup) Add(delta int) {
    // 获取状态码
    statep := wg.state()
    if race.Enabled {
        _ = *statep // trigger nil deref early
        if delta < 0 {
            // Synchronize decrements with Wait.
            race.ReleaseMerge(unsafe.Pointer(wg))
        }
        race.Disable()
        defer race.Enable()
    }
    // 把传入的delta用原子操作加入到statep,
    state := atomic.AddUint64(statep, uint64(delta)<<32)
    // 获取计数器数值
    v := int32(state >> 32)
    // 获取等待数量
    w := uint32(state)
    if race.Enabled && delta > 0 && v == int32(delta) {
        // The first increment must be synchronized with Wait.
        // Need to model this as a read, because there can be
        // several concurrent wg.counter transitions from 0.
        race.Read(unsafe.Pointer(&wg.sema))
    }
    // 计数器小于0 报错
    if v < 0 {
        panic("sync: negative WaitGroup counter")
    }
    
    if w != 0 && delta > 0 && v == int32(delta) {
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }
    // 如果等待为0或者计数器大于0 意味着没有等待或者还有读锁 不需要唤醒goroutine则返回 add操作完毕
    if v > 0 || w == 0 {
        return
    }
    
    if *statep != state {
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }
    // 
    // 唤醒所有等待的线程
    for ; w != 0; w-- {
        runtime_Semrelease(&wg.sema, false)
    }
}

// Done函数 调用了Add函数传入-1 相当于锁的数量减1
func (wg *WaitGroup) Done() {
    wg.Add(-1)
}

func (wg *WaitGroup) Wait() {
    // 获取waitGroup的状态码
    statep := wg.state()
    if race.Enabled {
        _ = *statep // trigger nil deref early
        race.Disable()
    }
    // 循环
    for {
        // 调用load获取状态
        state := atomic.LoadUint64(statep)
        // 获取计数器数值
        v := int32(state >> 32)
        // 获取等待数量
        w := uint32(state)
        
        if v == 0 {
            // Counter is 0, no need to wait.
            if race.Enabled {
                race.Enable()
                race.Acquire(unsafe.Pointer(wg))
            }
            return
        }
        // 添加等待数量 如果cas失败则重新获取状态 避免计数有错
        if atomic.CompareAndSwapUint64(statep, state, state+1) {
            if race.Enabled && w == 0 {
                // Wait must be synchronized with the first Add.
                // Need to model this is as a write to race with the read in Add.
                // As a consequence, can do the write only for the first waiter,
                // otherwise concurrent Waits will race with each other.
                race.Write(unsafe.Pointer(&wg.sema))
            }
            // 阻塞goroutine 等待唤醒
            runtime_Semacquire(&wg.sema)
            if *statep != 0 {
                panic("sync: WaitGroup is reused before previous Wait has returned")
            }
            if race.Enabled {
                race.Enable()
                race.Acquire(unsafe.Pointer(wg))
            }
            return
        }
    }
}

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:大二小的宝

查看原文:Go WaitGroup 源码分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

440 次点击  
加入收藏 微博
上一篇:C语言调用GO
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传