Golang WaitGroup源码分析

Stella981
• 阅读 656

针对Golang 1.9的sync.WaitGroup进行分析,与Golang 1.10基本一样除了将panic改为了throw之外其他的都一样。 源代码位置:sync\waitgroup.go

结构体

type WaitGroup struct {
    noCopy noCopy  // noCopy可以嵌入到结构中,在第一次使用后不可复制,使用go vet作为检测使用
    // 位值:高32位是计数器,低32位是goroution等待计数。
    // 64位的原子操作需要64位的对齐,但是32位。编译器不能确保它,所以分配了12个byte对齐的8个byte作为状态。
    state1 [12]byte // byte=uint8范围:0~255,只取前8个元素。转为2进制:0000 0000,0000 0000... ...0000 0000
    sema   uint32   // 信号量,用于唤醒goroution
}

不知道大家是否和我一样,不论是使用Java的CountDownLatch还是Golang的WaitGroup,都会疑问,可以装下多个线程|协程等待呢?看了源码后可以回答了,可以装下

1111 1111 1111 ... 1111
\________32___________/

2^32个辣么多!所以不需要担心单机情况下会被撑爆了。

函数

以下代码已经去掉了与核心代码无关的race代码。

Add

添加或者减少等待goroutine的数量。

参数delta可能是负的,加到WaitGroup计数器,可能出现如下结果

  • 如果计数器变为零,所有被阻塞的goroutines都会被释放。

  • 如果计数器变成负数,就增加恐慌。

    func (wg *WaitGroup) Add(delta int) { // 获取到wg.state1数组中元素组成的二进制对应的十进制的值 statep := wg.state() // 高32位是计数器 state := atomic.AddUint64(statep, uint64(delta)<<32) // 获取计数器 v := int32(state >> 32) w := uint32(state) // 计数器为负数,报panic if v < 0 { panic("sync: negative WaitGroup counter") } // 添加与等待并发调用,报panic if w != 0 && delta > 0 && v == int32(delta) { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // 计数器添加成功 if v > 0 || w == 0 { return } // 当等待计数器> 0时,而goroutine设置为0。 // 此时不可能有同时发生的状态突变: // - 增加不能与等待同时发生, // - 如果计数器counter == 0,不再增加等待计数器 if *statep != state { panic("sync: WaitGroup misuse: Add called concurrently with Wait") } // Reset waiters count to 0. *statep = 0 for ; w != 0; w-- { // 目的是作为一个简单的wakeup原语,以供同步使用。true为唤醒排在等待队列的第一个goroutine runtime_Semrelease(&wg.sema, false) } }

    // unsafe.Pointer其实就是类似C的void *,在golang中是用于各种指针相互转换的桥梁。 // uintptr是golang的内置类型,是能存储指针的整型,uintptr的底层类型是int,它和unsafe.Pointer可相互转换。 // uintptr和unsafe.Pointer的区别就是:unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算; // 而uintptr是用于指针运算的,GC 不把 uintptr 当指针,也就是说 uintptr 无法持有对象,uintptr类型的目标会被回收。 // state()函数可以获取到wg.state1数组中元素组成的二进制对应的十进制的值 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])) } }

Done

相当于Add(-1)。

func (wg *WaitGroup) Done() {
    // 计数器减一
    wg.Add(-1)
}

Wait

执行阻塞,直到所有的WaitGroup数量变成0。

func (wg *WaitGroup) Wait() {
    // 获取到wg.state1数组中元素组成的二进制对应的十进制的值
    statep := wg.state()
    // cas算法
    for {
        state := atomic.LoadUint64(statep)
        // 高32位是计数器
        v := int32(state >> 32)
        w := uint32(state)
        // 计数器为0,结束等待
        if v == 0 {
            // Counter is 0, no need to wait.
            return
        }
        // 增加等待goroution计数,对低32位加1,不需要移位
        if atomic.CompareAndSwapUint64(statep, state, state+1) {
            // 目的是作为一个简单的sleep原语,以供同步使用
            runtime_Semacquire(&wg.sema)
            if *statep != 0 {
                panic("sync: WaitGroup is reused before previous Wait has returned")
            }
            return
        }
    }
}

使用注意事项

  1. WaitGroup不能保证多个 goroutine 执行次序
  2. WaitGroup无法指定固定的goroutine数目
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Souleigh ✨ Souleigh ✨
2年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这