golang WaitGroup的使用与底层实现

使用的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操作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI 文本转视频(视频生产工具分享)

AI 文本转视频&#xff08;视频生产工具分享&#xff09; 介绍 ​ 想要根据任何描述轻松创建有趣的视频吗&#xff1f;然后&#xff0c;您应该尝试使用人工智能视频生成工具。毫无疑问&#xff0c;人工智能是未来。人工智能视频生成器可以轻松地从任何文本制作视频。只需几分…

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

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

为何要隐藏IP地址?代理ip在网络安全和隐私保护中的作用是什么?

目录 前言 一、为何要隐藏IP地址&#xff1f; 1. 保护隐私。 2. 防止网络攻击。 3. 避免限制和审查。 二、网络上哪些行为需要隐藏IP和更换IP&#xff1f; 1. 下载种子文件。 2. 访问受限网站。 3. 保护网络隐私。 4. 避免被封禁。 三、代理IP在网络安全和隐私保护中…

数据结构-04-队列

1-队列的结构和特点 生活中我们排队买票&#xff0c;先来的先买&#xff0c;后来的人只能站末尾&#xff0c;不允许插队。先进者先出&#xff0c;这就是典型的"队列"。队列跟栈非常相似&#xff0c;支持的操作也很有限&#xff0c;最基本的操作也是两个&#xff1a;入…

Paraformer 语音识别原理

Paraformer(Parallel Transformer)非自回归端到端语音系统需要解决两个问题&#xff1a; 准确预测输出序列长度&#xff0c;送入预测语音信号判断包含多少文字。 如何从encoder 的输出中提取隐层表征&#xff0c;作为decoder的输入。 采用一个预测器&#xff08;Predictor&…

windows配置go调用python的编译环境

go是支持调用python代码的&#xff0c;之前写了几篇linux的部署教程&#xff0c;因为觉得windows的不复杂就没有写&#xff0c;结果今天新部署一个Windows的环境&#xff0c;有些步骤想不起来了&#xff0c;好记性不如烂笔头&#xff0c;还是记录一下吧。 这些是之前写的linux…

Vue3Element-plus编写一个简版的字典服务

之前公司有维护过一个内部的字典平台,基本步骤和页面如下 添加字典属性弹窗 添加枚举值弹窗 基本业务代码如下 核心代码 import { defineStore } from pinia export const useDictionary defineStore(dictionary, {state: () > ({dict: [],dictObj: {},}),actions: {s…

C语言-指针讲解(4)

在上一篇博客中&#xff1a; C语言-指针讲解(3) 我们给大家介绍了指针进阶的用法 让下面我们来回顾一下讲了什么吧&#xff1a; 1.字符指针变量类型以及用法 2.数组指针本质上是一个指针&#xff0c;里面存放数组的地址。而指针数组本质上是个数组&#xff0c;里面存放的是指针…

知识图谱最简单的demo实现

一、简介 知识图谱整个建立过程可以分为以下几点&#xff1a; #mermaid-svg-zJuLB8k8EgBQF8M0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-zJuLB8k8EgBQF8M0 .error-icon{fill:#552222;}#mermaid-svg-zJuLB8k8E…

图片点击放大

在列表中添加插槽 <template slot-scope"scope">&#xff0c;获取当前点击的数据 在图片中添加点击事件的方法&#xff0c;用来弹出窗口 <vxe-columnfield"icon"title"等级图标"><template slot-scope"scope"><…

Kubernetes(K8s) Pod详解-05

Pod详解 Pod介绍 Pod结构 每个Pod中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类&#xff1a; 用户程序所在的容器&#xff0c;数量可多可少 Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个&#xff1a; 可以以它为依据…

hadoop完全分布式搭建

文章目录 集群部署规划服务器准备Mobaxterm 远程登录实验前准备安装软件工具关闭防火墙 安装 JDK 和 Hadoop创建软件包目录解压软件包配置环境变量 集群搭建先创建 HDFS 工作目录和 LOG 目录配置集群配置环境配置 HDFS 主节点信息、持久化和数据文件的主目录配置 HDFS 默认的数…

程序员养生之道:延寿不忘初心——延寿必备

文章目录 每日一句正能量前言如何养生饮食篇运动篇休息篇后记 每日一句正能量 现代社会已不是大鱼吃小鱼的年代&#xff0c;而是快鱼吃慢鱼的年代。 前言 在IT行业中&#xff0c;程序员是一个重要的职业群体。由于长时间的繁重编程工作&#xff0c;程序员们常常忽略了身体健康…

Unity中Shader编译目标渲染器

文章目录 前言一、Unity在打包时&#xff0c;会把Shader编译成不同平台对应的代码我们在状态栏&#xff0c;可以看见我们目前所处于的目标平台 二、在Unity中&#xff0c;怎么指定目标平台1、#pragma only_renderers2、#pragma exclude_renderers 三、我们测试一下看看效果1、 …

postman利用pre-request script自动设置token

场景&#xff1a; 我们请求接口&#xff1a;/api/rest/user/list获取用户列表&#xff0c;但是该接口需要在header中带上Authorization表示的鉴权Token才行。 而登录接口/api/rest/login&#xff0c;则可以返回改Token 常规方案 我们先调登录接口/api/rest/login获取到Toke…

极简云网络验证系统开源源码

极简云验证&#xff0c;多样化应用管理方式&#xff0c;多种项目任你开发&#xff0c;分布式应用开关&#xff0c;让您的应用开发更简单&#xff0c;完美实现多用户多应用管理。 支持多应用卡密生成&#xff1a; 卡密生成 单码卡密 次数卡密 会员卡密 积分卡密 卡密管理 卡密长…

了解http协议

http的相关概念 互联网&#xff1a;是网络的网络&#xff0c;是所有类型网络的母集 因特网&#xff1a;世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上&#xff0c;大家把连接在因特网上的计算机都成为主机。 万维网&#xff1a;数据库 URL&#xff1a;万维…

亚马逊云科技向量数据库与生成式AI的完美融合:落地实践详解(四)

以往 OpenSearch 摄入时的一些最佳实践中并不包含 knn 的情况&#xff0c;所以在 knn 索引存在的情况&#xff0c;不能完全参照之前的结论&#xff0c;通过以上三种不同的实验方式&#xff0c;在多次实验的过程中&#xff0c;本文得到了以下的一些实践经验和结论&#xff0c;供…

自研分布式IM-HubuIM RFC草案

HubuIM RFC草案 消息协议设计 基本协议 评估标准 【性能】协议传输效率&#xff0c;尽可能降低端到端的延迟&#xff0c;延迟高于200ms用户侧就会有所感知 【兼容】既要向前兼容也要向后兼容 【存储】减少消息包的大小&#xff0c;降低空间占用率&#xff0c;一个字节在亿…

一键添加特效与色彩变化,视频剪辑高手助力创作炫酷短片!

亲爱的视频创作者们&#xff0c;想要让你的视频更加炫酷、吸引眼球吗&#xff1f;现在&#xff0c;我们有一款神奇的工具&#xff0c;可以帮助你一键添加特效与色彩变化&#xff0c;让你的视频瞬间焕发新活力&#xff01; 首先第一步&#xff0c;我们要进入视频剪辑高手并在上…