Golang的sync.WaitGroup 实现逻辑和源码解析
方便的并发,是Golang的一大特色优势,而使用并发,对sync包的WaitGroup不会陌生。WaitGroup主要用来做Golang并发实例即Goroutine的等待,当使用go启动多个并发程序,通过waitgroup可以等待所有go程序结束后再执行后面的代码逻辑,比如:
func Main() {
wg := sync.WaitGroup{}
for i := ; i < ; i++ {
wg.Add()
go func() {
defer wg.Done()
time.Sleep( * time.Second)
}() }
wg.Wait() // 等待在此,等所有go func里都执行了Done()才会退出
}
WaitGroup对外提供三个方法,Add(int),Done()和Wait(), 其中Done()是调用了Add(-1),一般使用方法是,先统一Add,在goroutine里并发的Done,然后Wait。
WaitGroup主要维护了2个计数器,一个是请求计数器 v,一个是等待计数器 w,二者组成一个64bit的值,请求计数器占高32bit,等待计数器占低32bit。
每次Add执行,请求计数器v加1,Done方法执行,请求计数器减1,v为0时通过信号量唤醒Wait()。
那么等待计数器拿来干嘛?是因为同一个实例的Wait()方法支持多处调用,每一次Wait()方法执行,等待计数器 w 就会加1,而当请求计数器v为0触发Wait()时,要根据w的数量发送w份的信号量,正确的触发所有的Wait(),这虽然不是常用的一个特性,但是在一些特殊场合是有用处的(比如多个并发都依赖于WaitGroup的实例的结束信号来进行下一个action),演示代码如下:
func main() {
wg := sync.WaitGroup{}
for i := ; i < ; i++ {
wg.Add()
go func() {
defer wg.Done()
}()
}
time.Sleep( * time.Second)
for j := ; j < ; j++ {
go func(i int) {
// 3个地方调用Wait(),通过等待j计时器,每个Wati都会被hu唤醒
wg.Wait()
fmt.Println("wait done now ", i)
}(j)
}
time.Sleep( * time.Second)
return
}
/*
输出如下,数字出现的顺序随机
wait done now 1
wait done now 0
wait done now 2
*/
同时,WaitGroup里还对使用逻辑进行了严格的检查,比如Wait()一旦开始不能Add().
下面是带注释的代码,去掉了不影响代码逻辑的trace部分:
func (wg *WaitGroup) Add(delta int) {
statep := wg.state()
// 更新statep,statep将在wait和add中通过原子操作一起使用
state := atomic.AddUint64(statep, uint64(delta)<<)
v := int32(state >> )
w := uint32(state)
if v < {
panic("sync: negative WaitGroup counter")
}
if w != && delta > && v == int32(delta) {
// wait不等于0说明已经执行了Wait,此时不容许Add
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// 正常情况,Add会让v增加,Done会让v减少,如果没有全部Done掉,此处v总是会大于0的,直到v为0才往下走
// 而w代表是有多少个goruntine在等待done的信号,wait中通过compareAndSwap对这个w进行加1
if v > || w == {
return
}
// This goroutine has set counter to 0 when waiters > 0.
// Now there can't be concurrent mutations of state:
// - Adds must not happen concurrently with Wait,
// - Wait does not increment waiters if it sees counter == 0.
// Still do a cheap sanity check to detect WaitGroup misuse.
// 当v为0(Done掉了所有)或者w不为0(已经开始等待)才会到这里,但是在这个过程中又有一次Add,导致statep变化,panic
if *statep != state {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// Reset waiters count to 0.
// 将statep清0,在Wait中通过这个值来保护信号量发出后还对这个Waitgroup进行操作
*statep =
// 将信号量发出,触发wait结束
for ; w != ; w-- {
runtime_Semrelease(&wg.sema, false)
}
} // Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
wg.Add(-)
} // Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
statep := wg.state()
for {
state := atomic.LoadUint64(statep)
v := int32(state >> )
w := uint32(state)
if v == {
// Counter is 0, no need to wait.
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(wg))
}
return
}
// Increment waiters count.
// 如果statep和state相等,则增加等待计数,同时进入if等待信号量
// 此处做CAS,主要是防止多个goroutine里进行Wait()操作,每有一个goroutine进行了wait,等待计数就加1
// 如果这里不相等,说明statep,在 从读出来 到 CAS比较 的这个时间区间内,被别的goroutine改写了,那么不进入if,回去再读一次,这样写避免用锁,更高效些
if atomic.CompareAndSwapUint64(statep, state, state+) {
if race.Enabled && w == {
// 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))
}
// 等待信号量
runtime_Semacquire(&wg.sema)
// 信号量来了,代表所有Add都已经Done
if *statep != {
// 走到这里,说明在所有Add都已经Done后,触发信号量后,又被执行了Add
panic("sync: WaitGroup is reused before previous Wait has returned")
}
return
}
}
}
Golang的sync.WaitGroup 实现逻辑和源码解析的更多相关文章
- Go语言备忘录:net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录:net/http包的使用模式和源码解析,多谢! 目录: 一.http ...
- Dubbo原理和源码解析之服务引用
一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...
- Dubbo原理和源码解析之“微内核+插件”机制
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- Go语言备忘录(3):net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢! 目录: 一.h ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Dubbo原理和源码解析之标签解析
一.Dubbo 配置方式 Dubbo 支持多种配置方式: XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现 属性配置:加载 classpath 根目录下的 dubbo.pr ...
- Dubbo原理和源码解析之服务暴露
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- rest-framework之视图和源码解析
视图和源码解析 通过使用mixin类编写视图: from rest_framework import mixins from rest_framework import generics class ...
随机推荐
- iOS多线程编程原理及实践
摘要:iOS开发中,开发者不仅要做好iOS的内存管理,而且如果你的iOS涉及多线程,那你也必须了解iOS编程中对多线程的限制,iOS主线程的堆栈大小为1M,其它线程均为512KB,且这个限制开发者是无 ...
- ArcEngine 创建要素,删除要素,生成网格,渲染图层(VB)
示例代码:https://github.com/yu969890202/ArcEngine/tree/master/WinFrom_ArcEngine_PointDistribution博客后面有两张 ...
- SEATA 分布式事务入门DEMO
Simple Extensible Autonomous Transacation Architecture,seata是简单的.可扩展.自主性高的分布式架构 SEATA Server Configu ...
- Render函数详解
一.虚拟dom DOM是文档对象模型(Document Object Model)的简写,在浏览器中通过js来操作DOM的操作性能很差,于是虚拟Dom应运而生.虚拟Dom就是在js中模拟DOM对象树来 ...
- Flash 上下文管理
1.Local() 作用:为每个协程或线程创建一个独立的内存空间 储存格式: { 唯一标识: {'stack': []} } 代码 try: from greenlet import getcurre ...
- [bzoj2004] [洛谷P3204] [Hnoi2010] Bus 公交线路
Description 小Z所在的城市有N个公交车站,排列在一条长(N-1)km的直线上,从左到右依次编号为1到N,相邻公交车站间的距 离均为1km. 作为公交车线路的规划者,小Z调查了市民的需求,决 ...
- Python环境搭建(win)——Python官方解释器
Python官方解释器搭建方法: 本文以当前最新的3.8.1为例 1.在电脑上打开Python的官网https://www.python.org/ 2.找到Download下的All releases ...
- linux DHCP 服务器
配置 1:/etc/dhcp.conf 配置文件 2:dhcp.leases 启动 dhcp 服务器 linux dhcp客户端 windows dhcp 客户端
- Android教程2020 - RecyclerView实际使用
示例,用RecyclerView的item做出一个列表. Android教程2020 - 系列总览 本文链接 前面我们已经知道如何用RecyclerView显示一列数据.这里我们做出一个具体的例子.尽 ...
- Linux 常用工具sysstat之sar
sysstat包 iostat.sar.sa1和sa2命令都是sysstat包的一部分.它是Linux包含的性能监视工具集合: sar:收集.报告或存储信息(CPU.内存.磁盘.中断.网卡.TTY.内 ...