方便的并发,是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. MapGIS注记文字无损转入ArcGIS软件

    在GIS软件中,注释是一种十分特殊的对象,虽然各类软件都支持注释,但它却不属于GIS的基本对象.因此通常的格式转换软件,都不对注释对象做特别的支持,我们最常见的Shape文件格式就只有点.线.面要素, ...

  2. kuangbin专题 专题九 连通图 Strongly connected HDU - 4635

    题目链接:https://vjudge.net/problem/HDU-4635 题目:有向图,给定若干个连通图,求最多还能添加几条边,添完边后,图仍然要满足 (1)是简单图,即没有重边或者自环 (2 ...

  3. cogs 49. 跳马问题 DFS dp

    49. 跳马问题 ★   输入文件:horse.in   输出文件:horse.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 有一只中国象棋中的 “ 马 ” ,在半张 ...

  4. SliverAppBar 介绍及使用

    SliverAppBar控件可以实现页面头部区域展开.折叠的效果,类似于Android中的CollapsingToolbarLayout.先看下SliverAppBar实现的效果,效果图如下: Sli ...

  5. idea实现svn拉分支和合并分支的教程

    原文地址:https://blog.csdn.net/qq_27471405/article/details/78498260 今天测试了一下svn拉分支和合并分支的教程,决定分享给大家 拉分支教程: ...

  6. 用自定义变量作为动作方法参数 URL路由 精通ASP-NET-MVC-5-弗瑞曼

  7. 使用Async方法 Using Async Methods 精通ASP-NET-MVC-5-弗瑞曼 Listing 4-32.

  8. 个人第四次作业AIpha2版本测试(最终版)

    这个作业属于哪个课程 软件工程 作业要求在哪里 作业要求 团队名称 RainbowPlan团队博客 这个作业目标 手动测试非本团队的小组程序,是否可以正常登录,正常运行 一.测试人员信息 测试人员 姓 ...

  9. Dynamics CRM CE 怎样从 UCI 改为 classic UI

    dynamics 现在大力推UCI. 但是对于大部分人来说还是使用不习惯. 怎样从UCI改为classic UI呢 1. 快速的方法 https://xxx.crm.dynamics.com/main ...

  10. jQuery学习总结(三)

    这篇文章讲的是jQuery里的ajax发送data的三种方式,利用ajax发送数据的好处是把数据发送到了servlet后,当前页面不进行跳转. jQuery的里的ajax发送data的方式主要有三种, ...