golang WaitGroup的使用与底层实现

news2025/1/9 16:32:29

使用的go版本为 go1.21.2

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

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	wg.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:23

type WaitGroup struct {
    //防止WaitGroup被复制, 君子协议,编译可以通过,某些编辑器会报waring
    //有兴趣可以看一下这里 https://github.com/golang/go/issues/8005#issuecomment-190753527
    noCopy noCopy

    // 高32位表示计数器,低32位表示等待的waiter数量。
    // 低版本go的state字段类型是[3]uint32,需要进行位数对齐
    state atomic.Uint64
    // 信号量
    sema  uint32
}
编辑器的warning

Add函数

//代码位于 GOROOT/src/sync/waitgroup.go L:43

func (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相同,说明当前是第一次调度add
	if 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更变就会触发这个panic
	if wg.state.Load() != state {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	// 重置waiter为0
	wg.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:91

func (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操作,会触发panic
			if 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.coloradmin.cn/o/1275189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Peter算法小课堂—差分与前缀和

差分 Codeforces802 D2C C代码详解 差分_哔哩哔哩_bilibili 一维差分 差分与前缀和可以说成减法和加法的关系、除法和乘法的关系、积分和微分的关系&#xff08;听不懂吧&#xff09; 给定数组A&#xff0c;S为A的前缀和数组&#xff0c;则A为S的差分数组 差分数组构造 现…

Unittest(1):unittest单元测试框架简介setup前置初始化和teardown后置操作

unittest单元测试框架简介 unittest是python内置的单元测试框架&#xff0c;具备编写用例、组 织用例、执行用例、功能&#xff0c;可以结合selenium进行UI自动化测 试&#xff0c;也可以结合appium、requests等模块做其它自动化测试 官方文档&#xff1a;https://docs.pytho…

opencv 图像边框

cv.copyMakeBorder() 图像设置边框或者填充

HarmonyOs 4 (一) 认识HarmonyOs

目录 一 HarmonyOs 背景1.1 发展时间线1.2 背景分析1.2.1 新场景1.2.2 新挑战1.2.3 鸿蒙生态迎接挑战 二 HarmonyOS简介2.1 OpenHarmony2.2 HarmonyOS Connect2.3 HarmonyOS Next**2.4 ArkTS &#xff08;重点掌握&#xff09;****2.5 ArkUI** 三 鸿蒙生态应用核心技术理念**3.…

c/c++概念辨析-指针常量常量指针、指针函数函数指针、指针数组数组指针

概念澄清&#xff1a; 统一规则&#xff1a; 不管是XX指针&#xff0c;还是指针XX&#xff0c;后者是本体&#xff0c;前者只是个定语&#xff0c;前者也可以替换为其他同类&#xff08;例如字符串&#xff09;&#xff0c;帮助理解。 XX指针&#xff1a; 可简单理解为&#…

骨传导耳机是智商税吗?骨传导耳机是利用什么原理听歌?

骨传导耳机并非智商税&#xff0c;而是一种新兴的技术产品。作为是一种新型的听音设备&#xff0c;它采用了与传统耳机不同的工作原理&#xff0c;通过将声音通过骨骼传导到内耳&#xff0c;实现了不用堵塞耳道就能听到声音的效果。相比传统耳机&#xff0c;骨传导耳机具有一些…

mac本地部署stable-diffusion

下载Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" ①输入“1”选择中科大版本&#xff0c;然后输入Y(YES)&#xff0c;直接输入开机密码&#xff08;不显示&#xff09;然后回车确认&#xff0c;开始下载 ②…

matlab 基于卡尔曼滤波的GPS-INS的数据融合的导航

1、内容简介 略 25-可以交流、咨询、答疑 2、内容说明 基于卡尔曼滤波的GPS-INS的数据融合的导航 "基于卡尔曼滤波的GPS-INS的数据融合的导航 基于卡尔曼滤波实现GPS-INS组合导航系统" 卡尔曼滤波、GPS、INS、数据融合、导航 3、仿真分析 4、参考论文 略 …

Linux基本指令(后篇)

目录 14.时间指令date 15.Cal指令 16.find指令(非常重要) 17.grep指令 18.打包压缩指令zip和tar以及解压指令unzip和tar 14.时间指令date date(显示当前时间) 1.在显示方面&#xff0c;使用者可以设定欲显示的格式&#xff0c;格式设定为一个加号后接数个标记&#xff0c;其中…

带头结点的双向循环链表

目录 带头结点的双向循环链表 1.存储定义 2.结点的创建 3.结点的初始化 4.尾插结点 5.尾删结点 6.头插结点 7.头删结点 8.查找并返回结点 9.在pos结点前插入结点 10.删除pos结点 11.打印链表 12.销毁链表 13.头插结点2.0版 14.尾插结点2.0版 前言&#xff1a; 当…

Mac右键添加通过VSCode打开

Mac右键添加通过VSCode打开 1 首先打开自动操作 进入方式 访达 – 应用程序 – 自动操作 2. 选择快速操作 3. 添加 最后 commands保存&#xff0c;可以输入自定义的名称 for f in "$" doopen -a "Visual Studio Code" "$f" done4. 找到保存的快…

SpringBoot+mysql+vue实现大学生健康档案管理系统前后端分离

一、项目简介 本项目是一套基于SpringBoot实现大学生健康档案管理系统&#xff0c;主要针对计算机相关专业的正在做bishe的学生和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目可以直接作为bishe使用。 项目都经过严格调试&#…

Java项目学生管理系统三添加学生

​ 欢迎来到本篇博客&#xff0c;昨天我们学习的是查询所有&#xff0c;今天我们将探索一个令人兴奋的话题——Java项目学生管理系统的学生添加功能。作为一个学生管理系统的关键功能之一&#xff0c;学生添加模块扮演着连接教育工作者与学生信息的桥梁。通过本文&#xff0c;我…

视频后期特效处理软件 Motion 5 mac中文版

Motion mac是一款运动图形和视频合成软件&#xff0c;适用于Mac OS平台。 Motion mac软件特点 - 精美的效果&#xff1a;Motion提供了多种高质量的运动图形和视频效果&#xff0c;例如3D效果、烟雾效果、粒子效果等&#xff0c;方便用户制作出丰富多彩的视频和动画。 - 高效的工…

还得是字节出来的,太秀了...

前段时间公司缺人&#xff0c;也面了许多测试&#xff0c;一开始瞄准的就是中级水准&#xff0c;当然也没指望能来大牛&#xff0c;提供的薪资在15-20k这个范围&#xff0c;来面试的人有很多&#xff0c;但是平均水平真的让人很失望。看了简历很多上面都是写有4年工作经验&…

(二)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)

一、无人机模型简介&#xff1a; 单个无人机三维路径规划问题及其建模_IT猿手的博客-CSDN博客 参考文献&#xff1a; [1]胡观凯,钟建华,李永正,黎万洪.基于IPSO-GA算法的无人机三维路径规划[J].现代电子技术,2023,46(07):115-120 二、Tiki-taka算法&#xff08;TTA&#xf…

zookeeper分布式先进先出队列 (实操课程)

本系列是zookeeper相关的实操课程&#xff0c;课程测试环环相扣&#xff0c;请按照顺序阅读来学习和测试zookeeper。 阅读本文之前&#xff0c;请先阅读----​​​​​​zookeeper 单机伪集群搭建简单记录&#xff08;实操课程系列&#xff09;zookeeper 客户端常用命令简单记录…

鸿蒙开发笔记

最近比较火&#xff0c;本身也是做前端的&#xff0c;就抽空学习了下。对前端很友好 原视频地址&#xff1a;黑马b站鸿蒙OS视频 下载安装跟着视频或者文档就可以了。如果你电脑上安装的有node&#xff0c;但是开发工具显示你没安装&#xff0c;不用动咱们的node&#xff0c;直…

高效复习的打印神器,学习好助手工作好搭档,咕咕机T1错题打印机上手

日常工作和学习的时候&#xff0c;经常需要打印很多电子表格和文档&#xff0c;但是手边不一定有打印机&#xff0c;对于在家或宿舍学习的学生来说更是如此&#xff0c;不过市面上常见的打印机需要墨盒、硒鼓等配件&#xff0c;使用成本高且不方便操作。那么有没有一款快捷易用…

智慧箱变监控系统

智慧箱变监控系统是一种先进的监控解决方案&#xff0c;依托电易云-智慧电力物联网&#xff0c;旨在实现对箱式变电站&#xff08;简称“箱变”&#xff09;的智能化、远程化监控和管理。以下是智慧箱变监控系统的主要功能和优势&#xff1a; 实时监测&#xff1a;通过传感器和…