WaitGroup

sync.WaitGroup 用于等待一组 goroutine 返回,如:

var wg = sync.WaitGroup{}func do() {time.Sleep(time.Second)fmt.Println("done")wg.Done()
}func main() {go do()go do()wg.Add(2)wg.Wait()fmt.Println("main done")
}

概览

如上面的例子, WaitGroup 只堆外暴露了三个方法:

// 等待的 goroutine 数加 delta
func (wg *WaitGroup) Add(delta int)
// 等待的 goroutine 数减一
func (wg *WaitGroup) Done()
// 阻塞,等待这一组 goroutine 全部退出
func (wg *WaitGroup) Wait()
type WaitGroup struct {noCopy noCopystate1 [3]uint32
}

WaitGroup 结构体中也只有两个字段:

  • noCopy: 用来保证不会被开发者错误拷贝
  • state1: 用来保存相关状态量

另外,他还提供了一个私有的方法用来获取状态和信号量

func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]} else {return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]}
}

statep 就是状态量,注意这里通过 unsafe 将 3 位数组(共 96 位)强转成了 uint64 这会导致部分数据丢失,具体来说,在64位的机器上会丢失最低 32 位,也即 state1[2] 在 32 位机器上会丢失最高 32 位,也即 state1[0], 这也是 64 位和 32 位机器上数组三位元素表示意义不同的原因。

强转之后,以 64 位机器为例,数组第二位会作为 statep 的高 32 位,第一位会作为 statep 的低 32 位,也就是说,此时 statep 的结构如下:

+----------------------+-----------------------+
|                      |                       |
|      Counter         |       Waiter          |
|                      |                       |
+----------------------+-----------------------+

Add

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

Done 其实就是对 Add 的一个封装。

func (wg *WaitGroup) Add(delta int) {statep, semap := wg.state()// 把 delta 加到 count 中state := atomic.AddUint64(statep, uint64(delta)<<32)// 获取 countv := int32(state >> 32)// 丢失高 32 位的 Counter, 得到 Waiterw := uint32(state)if v < 0 {panic("sync: negative WaitGroup counter")}// Waiter 不等于 0 说明现在还有 goroutine 没有 done, 这时是不允许 Add 的// 也即在 Wait 的过程中不允许通过 Add 添加 if w != 0 && delta > 0 && v == int32(delta) {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 正常修改 Counter 后返回if v > 0 || w == 0 {return}// 到这说明 Counter == 0 并且 delta 不是一个正数(执行 Done,并且是最后一次 Done)// 状态改变,说明有人在 Wait 过程中 Add 了if *statep != state {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 状态置 0*statep = 0// 唤醒 Wait 中的 goroutinefor ; w != 0; w-- {runtime_Semrelease(semap, false, 0)}
}

总结一下,首先 Done 只是对 Add 的简单封装,在 Add 时,通过巧妙利用精度丢失和位移运算分别计算出 add 后的 Counter 和 Waiter, 前者表示已经 add 了多少 Goroutine, 后者表示还有多少个 goroutine 需要 Wait, 这里需要注意,在 Wait 的过程中是不允许 Add 新 goroutine 的;在执行 Done 时,只是简单的将 Counter 减 1,直到 Counter == 1 时,也即最后一个 goroutine 已经执行完毕时,Done 会通知 Wait 停止阻塞,并将标志清空。

Wait

func (wg *WaitGroup) Wait() {statep, semap := wg.state()for {state := atomic.LoadUint64(statep)v := int32(state >> 32)// Counter == 0, 没有 Add, 直接返回if v == 0 {return}// 每一次 CAS 让 Waiter 加一,并进入阻塞,等待最后一个 Done 的 goroutine 将其唤醒if atomic.CompareAndSwapUint64(statep, state, state+1) {runtime_Semacquire(semap)if *statep != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}return}// 如果 CAS 比较没通过,说明在此过程中有 goroutine Done 了,需要重新去获取最新的状态}
}

总结

WaitGroup 用于阻塞某个 Goroutine 以等待一组 goroutine 返回,在实现上,它采用一个长度为 3 的 32 位无符号整型数组保存 Waiter, Counter, 和信号量,每次 Add 时,会将 Counder 加上 delta,而当执行 Done 或 delta 为负数时,如果 Done 的是最后一个 Goroutine, Add 会去唤醒 Wait

执行 Wait 只是将 Waiter 加一并阻塞等待 Add 的唤醒,所以其实 Waiter 的值只会是 0 或 1.

【Go】sync.WaitGroup 源码分析相关推荐

  1. go sync.WaitGroup源码分析

    go版本 :1.10.3 原理实现:信号量 信号量是Unix系统提供的一种保护共享资源的机制,用于防止多个线程同时访问某个资源. 可简单理解为信号量为一个数值: 当信号量>0时,表示资源可用,获 ...

  2. go sync.map 源码分析

    一 简言 1.1 文件所在目录:go/src/sync/map.go 1.2 本篇博客的go版本:go1.10.3 二 原理说明 空间换时间.通过冗余的两个数据结构(read.dirty),实现加锁对 ...

  3. 【Go】sync.RWMutex源码分析

    RWMutex 读写锁相较于互斥锁有更低的粒度,它允许并发读,因此在读操作明显多于写操作的场景下能减少锁竞争的次数,提高程序效率. type RWMutex struct {w Mutex // he ...

  4. Golang sync.Mutex源码分析

    sync.Mutex是一个不可重入的排他锁. 这点和Java不同,golang里面的排它锁是不可重入的.当一个 goroutine 获得了这个锁的拥有权后, 其它请求锁的 goroutine 就会阻塞 ...

  5. Kubernetes监控之Heapster源码分析

    源码版本 heapster version: release-1.2 简介 Heapster是Kubernetes下的一个监控项目,用于进行容器集群的监控和性能分析. 基本的功能及概念介绍可以回顾我之 ...

  6. k8s源码分析--kube-scheduler源码(一)

    版本:v1.13.0 启动分析 kubernetes基础组件的入口均在cmd目录下,kube-schduler入口在scheduler.go下. kubernetes所有的组件启动采用的均是comma ...

  7. kube-scheduler源码分析(五)之 PrioritizeNodes

    本文个人博客地址:https://www.huweihuang.com/kubernetes-notes/code-analysis/kube-scheduler/PrioritizeNodes.ht ...

  8. kube-scheduler源码分析(四)之 findNodesThatFit

    本文个人博客地址:https://www.huweihuang.com/kubernetes-notes/code-analysis/kube-scheduler/findNodesThatFit.h ...

  9. Metricbeat源码分析

    0X00 版本信息 Golang:1.16.8 Metricbeat:7.14 0X01 Metricbeat介绍 Metricbeat quick start: installation and c ...

最新文章

  1. 搜索进程内存地址_Linux编程 6 (查看进程 ps 及输出风格)
  2. 最近5年,诺贝尔化学奖都颁给了谁?
  3. 在linux上使用cvs命令
  4. 在python中、下列代码的输出是什么-Python 面试中 8 个必考问题
  5. BZOJ4155 : [Ipsc2015]Humble Captains
  6. uos系统断网怎么安装mysql_【学习笔记】 UOS安装MySQL
  7. java中的基本数据类型(四类八种)
  8. 前端学习(2934):上午回顾
  9. 创建mysql代码实例_MySQL筹建系列之多实例_mysql
  10. json无法解析的字符
  11. 红黑树-想说爱你不容易
  12. Java Integer类
  13. 改变输出格式,提高手机照片质量
  14. JAVA程序设计(学堂在线-清华大学) 课后练习题 已更新完毕
  15. 【项目实践】充电台灯电路拆解
  16. qcap 教程_高通平台抓取ramdump及使用qcap解析,ramdumpqcap
  17. c语言水文水资源,长江中游水文网
  18. 帝国CMS文章内容超级伪静态 去除栏目id 以自定义英文名称和ID组成
  19. java 菱形继承_菱形继承与菱形虚拟继承
  20. aria2 网页版服务器错误,aria2服务器错误

热门文章

  1. html 基本布局介绍
  2. HZOJ 大佬(kat)
  3. codeforce 457DIV2 C题
  4. JavaScript学习笔记(四)——jQuery插件开发与发布
  5. shell编程-变量
  6. JAVA 操作系统已经来到第五个版本了 现陆续放出三个版本 这是第二个版本
  7. 谈谈C#中的三个关键词new , virtual , override(装载 Winner.Net)
  8. cAdvisor+InfluxDB+Grafana 监控Docker
  9. 纯html5+css3能写出什么惊人效果?
  10. redux 源码详解