golang sync.WaitGroup 源码分析

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

最近在学习golang源码,学习golang源码是学习golang的非常好的途径。

先来记录一波sync包的学习。版本 go1.14.2 darwin/amd64

sync.WaitGroup

我们一般使用sync.waitGroup 做并发控制,使用方式一般如下

func main() {
    wg := sync.WaitGroup{}

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            fmt.Println(index)
        }(i)
    }
    
    wg.Wait()
}

所以我们先看下waitGroup 主要有是哪个函数,Add, Done, Wait 函数,在看函数源码前,我们先看下WaitGroup 结构

type WaitGroup struct {
    noCopy noCopy          //禁止拷贝,如果有拷贝构建不会报错,可以用go vet 或 go tool vet 检测是否有拷贝错误,
    state1 [3]uint32      //重要,存有 计数器,等待数,信号量的值
}

其中 state1 成员存放的值在64 位系统中如下:

<img src="/Users/guanjingyun/Library/Application Support/typora-user-images/image-20201226143123260.png" alt="image-20201226143123260" style="zoom:33%;" />

接下来看下Add方法

func (wg *WaitGroup) Add(delta int) {
  statep, semap := wg.state()   //获取wg状态,statep地址(高32位的计数器值和低32位的等待数量值), semap 信号量
    if race.Enabled {  //数据竞态检测,默认是false,开启消耗cpu性能 ,先不管
        _ = *statep
        if delta < 0 { 
            race.ReleaseMerge(unsafe.Pointer(wg))
        }
        race.Disable()
        defer race.Enable()
    }
    state := atomic.AddUint64(statep, uint64(delta)<<32) //原子操作给计数器加上delta的值
    v := int32(state >> 32)     //高32位 计数器
    w := uint32(state)   //低32位 等待数
    if race.Enabled && delta > 0 && v == int32(delta) {  //数据竞态检测,先不管
        race.Read(unsafe.Pointer(semap))
    }
    if v < 0 {                                  //计数器 <0 
        panic("sync: negative WaitGroup counter")
    }
    if w != 0 && delta > 0 && v == int32(delta) {    //在add之前,已经调用过wait函数??(没太看明白)
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }
    if v > 0 || w == 0 { // wait数 为0 或 计数>0 直接返回
        return
    }

    if *statep != state {   // ? 理论上应该相等
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }
    
    *statep = 0
    for ; w != 0; w-- {  //计数器 为0, 释放所有wait 信号量,
        runtime_Semrelease(semap, false, 0)
    }
}

Done函数, 很简单了:

// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
    wg.Add(-1)
}

Wait函数, 把数据竞态检测 部分去了,为代码看起来简洁

func (wg *WaitGroup) Wait() {
    statep, semap := wg.state()  //获取wg状态,statep地址(高32位的计数器值和低32位的等待数量值), semap 信号量
    for {
        state := atomic.LoadUint64(statep) 
        v := int32(state >> 32)  //如上
        w := uint32(state)
        if v == 0 {  //计数为0,不用wait
            return
        }

        if atomic.CompareAndSwapUint64(statep, state, state+1) { //等待数加1
            runtime_Semacquire(semap)    //获取信号量
            if *statep != 0 {
                panic("sync: WaitGroup is reused before previous Wait has returned")
            }
            return
        }
    }
}

runtime_Semacquire 和 runtime_Semrelease 获取和释放信号量,用于休眠和唤醒 协程,具体实现等下次深入研究下


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

本文来自:Segmentfault

感谢作者:云溪小筑

查看原文:golang sync.WaitGroup 源码分析

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

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