前言

waitgroup也是一个非常有用的并发工具,有点像是Java中的CyclicBarrier,只不过Go中的WaitGroup等待的是协程而已。 通常来说,WaitGroup是go并发中最常用的工具了,在起协程并发做一些事儿,我们可以通过WaitGroup了表达这一组协程的任务是否完成,已决定是否继续往下走,或者取任务结果,下面来看一下WaitGroup的使用及实现。

语法基础

WaitGroup的核心关注点是:Add()、Done()、Wait()三个函数 Add函数主要为WaitGroup的等待数+1或者+n Done函数调用的也是Add函数,主要用于-1操作 Wait函数是指阻塞当前协程,直到等待数归为0才继续向下执行 下面来看一个demo:

func main() {waitGroup := &sync.WaitGroup{}DoSomething(waitGroup)waitGroup.Wait() // 这里会阻塞main,直到所有的任务都完成fmt.Println("end")
}func DoSomething(waitGroup *sync.WaitGroup) {for i:=0;i <10;i++ {waitGroup.Add(1)go func(waitGroup *sync.WaitGroup) {fmt.Print("1-")defer waitGroup.Done()}(waitGroup)}
}

输出:

实现原理

首先来看WaitGroup的结构体:

type WaitGroup struct {noCopy noCopystate1 [3]uint32
}

noCopy共64位,高32位是一个计数器,低32位代表有多少在等待,64位原子操作需要64位对齐,但32位编译器不能确保对齐。因此,我们分配12个字节,然后使用其中对齐的8个字节作为状态,另外4个字节作为SEMA的存储。 state1 保存的是状态信息 接下来看一下核心的两个函数Add()、Wait() Add(): 1、添加将delta(可能为负)加到waitgroup计数器上。 2、如果计数器变为零,则所有在等待时阻塞的goroutine都被释放。 3、如果计数器为负,则直接panic。

需要注意的是: 1、当计数器为零时发生的具有正增量的调用必须在等待之前发生,但任何时候都可能发生负增量的调用。这意味着要Add的函数调用应该在创建goroutine或等待的其他事件的语句之前执行。 2、如果waitgroup被重用以等待几个独立的事件集,则必须在返回所有以前的wait调用之后进行新的add调用。

func (wg *WaitGroup) Add(delta int) {statep, semap := wg.state()if race.Enabled {_ = *statep // trigger nil deref earlyif delta < 0 {// 与wait变为同步操作race.ReleaseMerge(unsafe.Pointer(wg))}race.Disable()defer race.Enable()}state := atomic.AddUint64(statep, uint64(delta)<<32)v := int32(state >> 32)w := uint32(state)if race.Enabled && delta > 0 && v == int32(delta) {//第一个增量必须与wait同步。
//需要将其变为为一个读取,因为可以有几个从0开始的并发wg.counter转换。race.Read(unsafe.Pointer(semap))}if v < 0 {panic("sync: negative WaitGroup counter")}if w != 0 && delta > 0 && v == int32(delta) {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}if v > 0 || w == 0 {return}
//当waiters>0时,此goroutine已将counter设置为0。
//现在状态不能同时突变:
//-添加不能与等待同时发生,
//-如果看到counter==0,则wait不会增加waiters。
//仍然执行简单的检查以检测WaitGroup的误用。if *statep != state {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// Reset waiters count to 0.*statep = 0for ; w != 0; w-- {runtime_Semrelease(semap, false)}
}

Wait(): Wait函数主要用于阻塞那些调用Wait的goroutine,直到waitgroup的任务计数器为0,才会放行,下面来看一下源码:

func (wg *WaitGroup) Wait() {statep, semap := wg.state()if race.Enabled {_ = *stateprace.Disable()}// 循环检查计数器情况for {state := atomic.LoadUint64(statep)v := int32(state >> 32)w := uint32(state)if v == 0 {// 如果技术为0的话就不用等待了,完成放行if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(wg))}return}// 如果有有新的调用wait函数的,添加新的等待者,等待者计数器+1if atomic.CompareAndSwapUint64(statep, state, state+1) {if race.Enabled && w == 0 {// 等待和新添加的是同步的// 只能为第一个竞争者进行写操作,否则的话会产生互相竞争race.Write(unsafe.Pointer(semap))}runtime_Semacquire(semap)if *statep != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(wg))}return}}
}

关于WaitGroup暂时介绍这么多。

go int32不能打印0_Go并发实战--sync WaitGroup相关推荐

  1. 并发安全Sync包的使用

    有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态).Sync包主要实现了并发任务同步WaitGroup的几种方法和并发安全的互斥锁和读写锁 ...

  2. Go语言管道与高并发实战

    固定时长定时器 写法一 package mainimport ("fmt""time" )func main() {//创建3秒定时器timer := time ...

  3. C#并发实战Parallel.ForEach使用

    C#并发实战Parallel.ForEach使用 原文:C#并发实战Parallel.ForEach使用 前言:最近给客户开发一个伙食费计算系统,大概需要计算2000个人的伙食.需求是按照员工的预定报 ...

  4. java高并发实战Netty+协程(Fiber)|系列1|事件驱动模式和零拷贝

    今天开始写一些高并发实战系列. 本系列主要讲两大主流框架: Netty和Quasar(java纤程库) 先介绍netty吧,netty是业界比较成熟的高性能异步NIO框架. 简单来说,它就是对NIO2 ...

  5. 03 重修C++之并发实战3.5-3.8(3end)

    上一篇:03 重修C++之并发实战3.3-3.4 03 重修C++之并发实战3.5-3.8(3end) 文章目录 03 重修C++之并发实战3.5-3.8(3end) 3.5 用 std::uniqu ...

  6. 高并发实战2---手写计算器缓存

    对于初级版本(高并发实战1)的提升一级优化 不直接缓存计算结果,而是缓存计算任务(future可以阻塞线程),当没有从缓存中读到正在执行计算的任务的时候,直接阻塞等待正在执行的任务计算的结果,然后读取 ...

  7. R语言print函数和cat函数打印数据对象实战

    R语言print函数和cat函数打印数据对象实战 目录 R语言print函数和cat函数打印数据对象实战 #基本语法

  8. java后验条件_JAVA并发实战学习笔记——3,4章~

    JAVA并发实战学习笔记 第三章 对象的共享 失效数据: java程序实际运行中会出现①程序执行顺序对打乱:②数据对其它线程不可见--两种情况 上述两种情况导致在缺乏同步的程序中出现失效数据这一现象, ...

  9. 《Go语言实战》笔记之协程同步 sync.WaitGroup

    原文地址(欢迎互换友链): http://www.niu12.com/article/8 sync 包提供同步 goroutine 的功能 <p>文档介绍</p><cod ...

最新文章

  1. JavaScript 表单编程
  2. Zookeeper客户端
  3. qtabwidget设置tab高度_VC|富文本编辑框CRichEditCtrl的字体与段落设置
  4. 第三十三期:对于人工智能的恐惧及其5个解决方法
  5. C11标准委员会成员解读C语言新标准
  6. exchange2010查询用户邮箱配额、设置用户邮箱配置的方法
  7. php如何转换大小写,PHP转换大小写教程基础
  8. 多线程编程中锁的种类与应用举例
  9. hdu2492 Ping pong
  10. Windows蓝屏分析器WinDbg使用方法
  11. 基于C++的职工信息管理系统
  12. ISO 9001 质量标准体系 免费下载
  13. 《亲密关系》笔记(1)
  14. LED驱动芯片PT4115
  15. java 多线程抢票_多线程抢票详解
  16. 《华为研发》阅读 - 26 (中试部,品质保证)
  17. glib linux,如何升级glib呢?
  18. [LED]如何配置LCD背光和LED,调试方法
  19. web数据管理 期末
  20. zabbix监控第一篇---基础使用

热门文章

  1. 组件skype服务器,Skype for Business Server 中的中介服务器组件
  2. 在python中使用csv库以字典格式读写csv文件
  3. Python 开发工具链全解
  4. python 利用 for ... else 跳出双层嵌套循环
  5. Python属性访问拦截器的用法
  6. python flask 如何修改默认端口号
  7. signature=5edb6549fd9da7abd1cea3d5008f5c15,IMPLEMENTING INVERTED MASTER-SLAVE 3D SEMICONDUCTOR STACK
  8. mysql数据库断开连接_解决mysql服务器在无操作超时主动断开连接的情况
  9. c#后台如何导出excel到本地_C#后台导出Excel
  10. 深度学习NCHW和NHWC数据格式(由三维数据转换成一维数据的遍历方式)