golang WaitGroup的使用与底层实现

2023-11-30 12:28

本文主要是介绍golang WaitGroup的使用与底层实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用的go版本为 go1.21.2

首先我们写一个简单的WaitGroup的使用代码

package mainimport ("fmt""sync"
)func main() {var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()fmt.Println("xiaochuan")}()wg.Wait()
}

WaitGroup的基本使用场景就是等待子协程完毕后,执行主协程,比如我的api需要多个下游api支持开多个协程进行访问,等待耗时最高的api返回过来后执行,这种场景是比较适合WaitGroup的。

我们来看一下WaitGroup构造体相关的底层源码

WaitGroup结构体

//代码位于 GOROOT/src/sync/waitgroup.go L:23type WaitGroup struct {//防止WaitGroup被复制, 君子协议,编译可以通过,某些编辑器会报waring//有兴趣可以看一下这里 https://github.com/golang/go/issues/8005#issuecomment-190753527noCopy noCopy// 高32位表示计数器,低32位表示等待的waiter数量。// 低版本go的state字段类型是[3]uint32,需要进行位数对齐state atomic.Uint64// 信号量sema  uint32
}
编辑器的warning

Add函数

//代码位于 GOROOT/src/sync/waitgroup.go L:43func (wg *WaitGroup) Add(delta int) {if race.Enabled { //使用竞态检查if delta < 0 { //如果传递的数值是负数,递减等待同步// Synchronize decrements with Wait.race.ReleaseMerge(unsafe.Pointer(wg))}race.Disable() //竞态检查 禁用defer race.Enable() //竞态检查 启用}//计算我们要进行add的值,将其加入到比特位上//<< 32 为二进制左位移 32位state := wg.state.Add(uint64(delta) << 32)v := int32(state >> 32) // state变量的高位是计数w := uint32(state) // state变量的低位是waiter计数//使用竞态检查,当前传入的值与v相同,说明当前是第一次调度addif race.Enabled && delta > 0 && v == int32(delta) {// The first increment must be synchronized with Wait.// Need to model this as a read, because there can be// several concurrent wg.counter transitions from 0.race.Read(unsafe.Pointer(&wg.sema))}//如果 计数器小于0 说明了多进行了done操作或者add传递负数,业务代码的出现逻辑错误了if v < 0 {panic("sync: negative WaitGroup counter")}// 如果当前存在等待,而且计数器不为0// 说明当前有地方调度了Wait后,又进行add操作了, 违反了官方的使用设计if w != 0 && delta > 0 && v == int32(delta) {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 计数大于0,没有等待,就是单纯的add直接返回if v > 0 || w == 0 {return}// 再做一次检测,防止有并发调度// 比如我有两个goroutine A goroutine 在add, B goroutine 在调度 wait // 刚刚好A加完了计数,B突然wait导致state更变就会触发这个panicif wg.state.Load() != state {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 重置waiter为0wg.state.Store(0)for ; w != 0; w-- { // 逐步释放信号量runtime_Semrelease(&wg.sema, false, 0)}
}

Done函数

//代码位于 GOROOT/src/sync/waitgroup.go L:86//这个很简单 调用了一下add函数传了一个-1
func (wg *WaitGroup) Done() {wg.Add(-1)
}

Wait函数

//代码位于 GOROOT/src/sync/waitgroup.go L:91func (wg *WaitGroup) Wait() {if race.Enabled { //使用竞态检查race.Disable() //竞态检查 禁用}for {state := wg.state.Load() // 原子操作读取state字段v := int32(state >> 32) // state变量的高位是计数w := uint32(state) // state变量的低位是waiter计数if v == 0 { // 如果当前计数器为0 就没必要等待直接返回了if race.Enabled {race.Enable() //竞态检查 启用race.Acquire(unsafe.Pointer(wg))}return}// 将waiter计数+1 因为waiter处于低32位所以不需要位移直接加就行了if wg.state.CompareAndSwap(state, state+1) {if race.Enabled && w == 0 { // 使用竞态检查,第一次进行wait操作// 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))}// 获取信号量,这行代码会进行G的阻塞runtime_Semacquire(&wg.sema)//重新获取一下state,正常来讲计数为0, waiter为0//执行判断之前,又有一个协程进行了add操作,会触发panicif wg.state.Load() != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}if race.Enabled { //使用竞态检查race.Enable() //竞态检查 启用race.Acquire(unsafe.Pointer(wg))}return}}
}

总结

我们从上面的源码分析了解WaitGroup的数据结构、Add、Done和Wait这些基本操作原理,在项目中我们可以使用比特位来减少内存的占用,从源码分析我们得知Go官方设计不允许进行WaitGroup复制(君子协议)与并发调度同一个WaitGroup操作。

这篇关于golang WaitGroup的使用与底层实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/437027

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

C#实现一键批量合并PDF文档

《C#实现一键批量合并PDF文档》这篇文章主要为大家详细介绍了如何使用C#实现一键批量合并PDF文档功能,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言效果展示功能实现1、添加文件2、文件分组(书签)3、定义页码范围4、自定义显示5、定义页面尺寸6、PDF批量合并7、其他方法

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2