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操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/236293.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何判断数据库慢 SQL 查询?

慢 SQL 查询通常指执行时间较长或者消耗大量系统资源的查询。要判断一个 SQL 查询是否慢&#xff0c;可以考虑以下几个方面&#xff1a; 执行时间&#xff1a; 观察查询执行所需的时间。如果一个查询花费了相对较长的时间才能返回结果&#xff0c;可能就是慢查询的一个指标。通…

计算机硬件(一)

1.机箱 计算机的许多硬件,如主板,硬盘和电源等,都安放在固定机箱中。机箱是一个相对封闭的空间&#xff0c;箱体一般由钢和铝合金等金属制成(其他材料亦可用&#xff0c;但不多见),同时设有许多通风口,以促进箱内空气流动,防止内部温度过高&#xff0c;机箱的颜色,大小乃至形状…

[Matlab有限元分析] 2.杆单元有限元分析

1. 一维杆单元有限元分析程序 一维刚单元的局部坐标系&#xff08;单元坐标系&#xff09;与全局坐标系相同。 1.1 线性杆单元 如图所示是一个杆单元&#xff0c;由两个节点i和j&#xff0c;局部坐标系的X轴沿着杆的方向&#xff0c;由i节点指向j节点&#xff0c;每个节点有…

XUbuntu22.04之OBS30.0设置录制音频降噪(一百九十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

ARM与大模型,狭路相逢

编辑&#xff1a;阿冒 设计&#xff1a;沐由 从去年底至今&#xff0c;伴随着OpenAI旗下ChatGPT的火爆&#xff0c;一波AI大模型推动着AI应用全面进入了大模型时代。与此同时&#xff0c;随着边缘算力的提升&#xff0c;AI大模型的部署也逐渐从云端涉入到边缘。 世界对AI算力的…

运维知识点-SQLServer/mssql

SQLServer/mssql Microsoft structed query language常见注入提权 技术点&#xff1a;0x00 打点前提 0x01 上线CS0x02 提权0x03 转场msf0x04 抓取Hash0x05 清理痕迹 Microsoft structed query language 常见注入 基于联合查询注入 order by 判断列数&#xff08;对应数据类型…

Vue中 实现自定义指令(directive)及应用场景

一、Vue2 1. 指令钩子函数 一个指令定义对象可以提供如下几个钩子函数 (均为可选)&#xff1a; bind 只调用一次&#xff0c;指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted 被绑定元素插入父节点时调用 (仅保证父节点存在&#xff0c;但不一定已…

【spring】bean的后处理器

目录 一、作用二、常见的bean后处理器2.1 AutowiredAnnotationBeanPostProcessor2.1.1 说明2.1.2 代码示例2.1.3 截图示例 2.2 CommonAnnotationBeanPostProcessor2.2.1 说明2.2.2 代码示例2.2.3 截图示例 2.3 ConfigurationPropertiesBindingPostProcessor2.3.1 说明2.3.2 代码…

华清远见嵌入式学习——C++——作业3

作业要求&#xff1a; 代码&#xff1a; #include <iostream>using namespace std;class Per { private:string name;int age;double *high;double *weight; public://有参构造函数Per(string n,int a,double h,double w):name(n),age(a),high(new double(h)),weight(ne…

geemap学习笔记015:下载哨兵2号(Sentinel-2)数据

前言 使用GEE下载数据应该是最常见的功能了&#xff0c;今天就介绍一下如何使用geemap下载哨兵2号(Sentinel-2)数据&#xff0c;分别包括自己画感兴趣&#xff0c;以及利用Assets中的shp文件进行下载。 1 自己画感兴趣下载哨兵2号影像 import geemap import eeMap geemap.M…

模板上新|2023年10月DataEase模板市场上新动态

DataEase开源数据可视化分析平台于2022年6月正式发布模板市场&#xff08;https://dataease.io/templates/&#xff09;。模板市场旨在为DataEase用户提供专业、美观、拿来即用的仪表板模板&#xff0c;方便用户根据自身的业务需求和使用场景选择对应的仪表板模板&#xff0c;并…

STM32/GD32_分散加载

Q&#xff1a;如何将一个变量、某个源文件的函数在编译阶段就存储在用户指定的区域&#xff1f; KEIL环境&#xff1a;.map后缀文件、.sct后缀文件 IAR环境&#xff1a;.map后缀文件、.icf后缀文件 【map文件】 对固件里面的变量、函数、常量等元素的存储空间进行分配的说明…