方便的并发,是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 实现逻辑和源码解析的更多相关文章

  1. Go语言备忘录:net/http包的使用模式和源码解析

    本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录:net/http包的使用模式和源码解析,多谢!  目录: 一.http ...

  2. Dubbo原理和源码解析之服务引用

    一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...

  3. Dubbo原理和源码解析之“微内核+插件”机制

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  4. Go语言备忘录(3):net/http包的使用模式和源码解析

    本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!  目录: 一.h ...

  5. Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  6. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  7. Dubbo原理和源码解析之标签解析

    一.Dubbo 配置方式 Dubbo 支持多种配置方式: XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现 属性配置:加载 classpath 根目录下的 dubbo.pr ...

  8. Dubbo原理和源码解析之服务暴露

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  9. rest-framework之视图和源码解析

    视图和源码解析 通过使用mixin类编写视图: from rest_framework import mixins from rest_framework import generics class ...

随机推荐

  1. ArcEngine DEM叠加影像

    代码执行前: 代码执行后: 影像叠加代码: /// <summary> /// 叠加DEM /// </summary> /// <param name="pR ...

  2. 构造函数以及关键词this

    Java中所有类都有构造方法,用来进行该类对象的初始化,构造方法也有名称,参数和方法体以及访问权限的设定. 1.构造方法的完整定义格式如下: [public|protected|private]< ...

  3. 如何使用poi在word表格中插入行的4种方法

    本文记录了,在word表格中插入新行的几种方法.直接上代码说明 table.addNewRowBetween 没实现,官网文档也说明,只有函数名,但没具体实现,但很多文章还介绍如何使用这个函数,真是害 ...

  4. Java 利用Map集合计算一个字符串中每个字符出现的次数

    步骤分析 1.给出一串字符串,字符串中可以包含字母.数字.符号等等. 2.创建一个Map集合,key是字符串中的字符,value是字符的个数. 3.遍历字符串,获取每一个字符. 5.使用获取到的字符, ...

  5. 网络流学习 - dinic

    推荐博客:https://www.cnblogs.com/SYCstudio/p/7260613.html#4246029

  6. 洛谷p1137 模拟退火

    题目链接:https://www.luogu.org/problem/P1337 以x为原点,将力分解成横纵方向的力,每次退火时单独对答案的横纵坐标进行判断是否更新答案 #include<ios ...

  7. Hibernate定制Tuplizer

    Hibernate定制Tuplizer,使用@Tuplizer注解 本文转自http://www.521100.net/hibernate-tuplizer.html 首先,第一步在Hinbernat ...

  8. noip2017考前基础复习——数论数学

    ·最大公约数 gcd 辗转相除法  gcd(a,b)=gcd(b,a%b) int gcd(int x,int y){ ) return x; return gcd(y,x%y); } 效率O(log ...

  9. arch_遇到的问题

    archlinux安装 wiki安装 可以参考这个来安装 $如果你跟我一样是用校园网安装的$ 记得使用pppoe-setup 来联网 arclinux 图形界面安装 参考 kde图形安装 需要创建一个 ...

  10. 异想家Win10系统安装的软件与配置

    1.C盘推荐一个硬盘,256G,安装好驱动,显卡配置好高性能,激活Win10,屏蔽WIn10驱动更新(Show or hide updates.diagcab),改电脑名称为Sandeepin-PC. ...