Go sync.WaitGroup的学习

news2025/1/20 11:59:51

一.前言

二. 夯实基础

2.1 sync.WaitGroup是什么?

Go语言中除了可以使用通道(channel)和互斥锁进行两个并发程序间的同步外,还可以使用等待组进行多个任务的同步,等待组可以保证在并发环境中完成指定数量的任务

在 sync.WaitGroup(等待组)类型中,每个 sync.WaitGroup 值在内部维护着一个计数,此计数的初始默认值为零。

等待组有下面几个方法可用,如下表所示。
在这里插入图片描述
对于一个可寻址的 sync.WaitGroup 值 wg:

  1. 我们可以使用方法调用 wg.Add(delta) 来改变值 wg 维护的计数。
  2. 方法调用 wg.Done() 和 wg.Add(-1) 是完全等价的。
  3. 如果一个 wg.Add(delta) 或者 wg.Done() 调用将 wg 维护的计数更改成一个负数,一个恐慌将产生
  4. 当一个协程调用了 wg.Wait() 时,
    • 如果此时 wg 维护的计数为零,则此 wg.Wait() 此操作为一个空操作(noop);
    • 否则(计数为一个正整数),此协程将进入阻塞状态。当以后其它某个协程将此计数更改至 0 时(一般通过调用 wg.Done()),此协程将重新进入运行状态(即 wg.Wait() 将返回)。

等待组内部拥有一个计数器,计数器的值可以通过方法调用实现计数器的增加和减少。当我们添加了 N 个并发任务进行工作时,就将等待组的计数器值增加 N。每个任务完成时,这个值减 1。同时,在另外一个 goroutine 中等待这个等待组的计数器值为 0 时,表示所有任务已经完成。

2.2 sync.WaitGroup的使用

sync.WaitGroup类型(以下简称WaitGroup类型)是开箱即用的,也是并发安全的。

WaitGroup类型拥有三个指针方法:Add、Done和Wait。你可以想象该类型中有一个计数器,它的默认值是0。我们可以通过调用该类型值的Add方法来增加,或者减少这个计数器的值。

一般情况下,我会用这个方法来记录需要等待的 goroutine 的数量。相对应的,这个类型的Done方法,用于对其所属值中计数器的值进行减一操作。我们可以在需要等待的 goroutine 中,通过defer语句调用它。

而此类型的Wait方法的功能是,阻塞当前的 goroutine,直到其所属值中的计数器归零。如果在该方法被调用的时候,那个计数器的值就是0,那么它将不会做任何事情。

三. 应用实践

3.1 sync.WaitGroup类型值中计数器的值可以小于0吗?

这里的典型回答是:不可以。

问题解析

为什么不可以呢,我们解析一下。之所以说WaitGroup值中计数器的值不能小于0,是因为这样会引发一个 panic。 不适当地调用这类值的Done方法和Add方法都会如此。别忘了,我们在调用Add方法的时候是可以传入一个负数的。

实际上,导致WaitGroup值的方法抛出 panic 的原因不只这一种。

你需要知道,在我们声明了这样一个变量之后,应该首先根据需要等待的 goroutine,或者其他事件的数量,调用它的Add方法,以使计数器的值大于0。这是确保我们能在后面正常地使用这类值的前提。

如果我们对它的Add方法的首次调用,与对它的Wait方法的调用是同时发起的,比如,在同时启用的两个 goroutine 中,分别调用这两个方法,那么就有可能会让这里的Add方法抛出一个 panic。

这种情况不太容易复现,也正因为如此,我们更应该予以重视。所以,虽然WaitGroup值本身并不需要初始化,但是尽早地增加其计数器的值,还是非常有必要的。

另外,你可能已经知道,WaitGroup值是可以被复用的,但需要保证其计数周期的完整性。这里的计数周期指的是这样一个过程:该值中的计数器值由0变为了某个正整数,而后又经过一系列的变化,最终由某个正整数又变回了0。

也就是说,只要计数器的值始于0又归为0,就可以被视为一个计数周期。在一个此类值的生命周期中,它可以经历任意多个计数周期。但是,只有在它走完当前的计数周期之后,才能够开始下一个计数周期。在这里插入图片描述
因此,也可以说,如果一个此类值的Wait方法在它的某个计数周期中被调用,那么就会立即阻塞当前的 goroutine,直至这个计数周期完成。在这种情况下,该值的下一个计数周期,必须要等到这个Wait方法执行结束之后,才能够开始。

如果在一个此类值的Wait方法被执行期间,跨越了两个计数周期,那么就会引发一个 panic

例如,在当前的 goroutine 因调用此类值的Wait方法,而被阻塞的时候,另一个 goroutine 调用了该值的Done方法,并使其计数器的值变为了0。

这会唤醒当前的 goroutine,并使它试图继续执行Wait方法中其余的代码。但在这时,又有一个 goroutine 调用了它的Add方法,并让其计数器的值又从0变为了某个正整数。此时,这里的Wait方法就会立即抛出一个 panic。

纵观上述会引发 panic 的后两种情况,我们可以总结出这样一条关于WaitGroup值的使用禁忌,即:不要把增加其计数器值的操作和调用其Wait方法的代码,放在不同的 goroutine 中执行。换句话说,要杜绝对同一个WaitGroup值的两种操作的并发执行。

除了第一种情况外,我们通常需要反复地实验,才能够让WaitGroup值的方法抛出 panic。再次强调,虽然这不是每次都发生,但是在长期运行的程序中,这种情况发生的概率还是不小的,我们必须要重视它们。

如果你对复现这些异常情况感兴趣,那么可以参看sync代码包中的 waitgroup_test.go 文件。其中的名称以TestWaitGroupMisuse为前缀的测试函数,很好地展示了这些异常情况的发生条件。

四.源码学习

五. 总结

sync代码包的WaitGroup类型和Once类型都是非常易用的同步工具。它们都是开箱即用和并发安全的。

利用WaitGroup值,我们可以很方便地实现一对多的 goroutine 协作流程,即:一个分发子任务的 goroutine,和多个执行子任务的 goroutine,共同来完成一个较大的任务。

在使用WaitGroup值的时候,我们一定要注意,千万不要让其中的计数器的值小于0,否则就会引发 panic。

另外,我们最好用“先统一Add,再并发Done,最后Wait”这种标准方式,来使用WaitGroup值。 尤其不要在调用Wait方法的同时,并发地通过调用Add方法去增加其计数器的值,因为这也有可能引发 panic。

5.1 高效编码技巧

  1. 最好用“先统一Add,再并发Done,最后Wait”这种标准方式,来使用WaitGroup值

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

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

相关文章

Spring Boot 中的Thymeleaf分页和排序示例

在上一篇文章中,我们已经知道如何构建Spring Boot Thymeleaf示例。今天,我将继续使用 Spring Data 和 Bootstrap 进行 Thymeleaf 分页和排序(按列标题)。 百里香叶分页和排序示例 假设我们在数据库中有这样的教程表:…

深入理解 Android 模块化里的资源冲突

翻译自 Understanding resource conflicts in Android ⚽ 前言 作为 Android 开发者,我们常常需要去管理非常多不同的资源文件,编译时这些资源文件会被统一地收集和整合到同一个包下面。根据官方的《Configure your build》文档介绍的构建过程可以总结这…

RFSoC应用笔记 - RF数据转换器 -22- API使用指南之配置DAC相关工作状态和中断相关函数使用

前言 本文完结后,关于RFSoC的配置的API函数部分就全部介绍完毕,后续有空将更新介绍简单的射频收发回环示例工程,不定时更新,敬请期待。 配置DAC相关工作状态 XRFdc_SetInterpolationFactor 函数原型 u32 XRFdc_SetInterpolat…

内存一致性,指令重排序,内存屏障,volatile解析

文章目录为什么会存在“内存可见性”问题重排序与内存可见性的关系as-if-serial语义单线程程序的重排序规则多线程程序的重排序规则happen-before是什么解决方案:内存屏障Volatile关键字解决内存可见性问题的实现原理为什么会存在“内存可见性”问题 下图为x86架构…

redis 的企业实战应用 (二)

前言: 如今redis的常用场景有 短信登录:使用redis共享session来实现 商户查询缓存:会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码…

【数学】仿射变换

∣降维打击NightguardSeries.∣\begin{vmatrix}\Huge{\textsf{ 降 维 打 击 }}\\\texttt{ Nightguard Series. }\end{vmatrix}∣∣∣∣∣​ 降 维 打 击 Nightguard Series. ​∣∣∣∣∣​ 注:本文讨论的仿射变换仅为y轴上的伸缩变换,且难度在高中生理…

H3CNE V7.0 视频教程

构建中小企业网络全套PPT汇总【V7版本】 第1章 计算机网络概述 第2章 OSI参考模型与TCP IP模型 第3章 局域网基本原理 第4章 广域网基本原理 第5章 IP基本原理 第6章 TCP和UDP基本原理 第7章 路由器、交换机及其操作系统介绍 第8章 命令行操作基础 第9章 网络设备文件…

mycat-3-实战篇

1 总结: 1:用的表必须在mycat的配置文件中配置。 2:mycat默认分片策略中,都是针对表的主键,默认是id,如果主键不是id的,请去rule.xml自己复制一份修改 3: 2 注意细讲解 1:schem…

Springboot启动流程分析(四):完成启动流程

目录 一 添加BeanPostProcessors到IOC容器 二 国际化支持 三 初始化监听器的多路播放器 四 刷新容器 五 注册监听器到IOC容器的多播器 六 完成bean的大规模实例化 6.1 大规模实例化bean 6.1.1 连续三层do...while循环作用 6.1.2 FactoryBean是什么?为什么要…

04 YAML kubetnetes世界里的通用语

文章目录1. 前言2. 声明式和命令式是怎么回事?3. 什么是YAML?4. 什么是API对象?4.1 k8s都有哪些资源对象4.2 列出kubectl 命令详细执行过程5. 如何描述 API 对象5.1 命令式5.2 声明式5.2.1 声明式YAML语法详解5.2.1.1 header部分详解5.2.1.2 …

【教学类-19-01】20221127《ABAB式-规律排序-A4竖版2份》(中班)

展示效果: 单人使用样式: 单页打印样式 ​ 背景需求: 中班幼儿需要掌握ABAB规律排序,如下图所示,AB两个元素能外形不同、颜色不同。 ​ ​利用Python Word单元格填色功能,随机生成AB样式,引…

STM32模拟IIC与IIC四种实现数字光强采集模块GY30(标准库与HAL库)

目录 代码实现是的IIC通信,数据采集后在串口显示,方便大家实现二次开发 原件选择 GY-30 数字光强度介绍 BH1750芯片参数 引脚说明 BH1750指令集 接线表设计 通过四种方式实现GY-30数据采集 1.标准库模拟IIC实现GY-30采集并串口1显示 2.标准库IIC…

重构uniapp uni-ui coloerUI项目

重构uniapp uni-ui coloerUI项目这里写自定义目录标题重构uniappuni-uicoloerUI项目起源流程重构uniappuni-uicoloerUI项目 起源 从网上复制了若依移动端的代码,但是对里面的文件夹布局方式和第三方组件库引入方式不甚了解,就想着从头创建一个空白项目,然后一步一…

Linux中设置开机启动执行命令和普通用户配置环境变量开机启动生效

记录:343 场景:在CentOS 7.9操作系统上,开机启动就执行自定义的命令,配置rc.local文件达到需求;在普通用户中配置环境变量开机启动生效,使用profile实现。 版本: 操作系统:CentOS…

01、Docker入门

目录 1、Docker是什么 2、Docker与虚拟化 3、Docker虚拟化的好处 好处一:应用部署方便 好处二:服务器同等配置,性能更优,利用率更高 4、核心概念 5、CentOS7 安装docker(在线方式) 6、镜像 7、Docker容器 8、查看Docker容…

typescript 八叉树的简单实现

查了一些文章,准备自己用typescript写一个简单的八叉树场景管理。 所谓的简单,就是所有元素都是边长为1的立方体。 元素类和树节点类 //元素类,因为都是边长为1的立方体,所以就用cube命名 export class CubeData {public reado…

由于没有远程桌面授权服务器可以提供许可证,远程会话连接已断开

一、问题描述 在使用Windows的远程桌面工具连接WindowsServer2016服务器时,无法连接到服务器,并且提示【由于没有远程桌面授权服务器可以提供许可证,远程回来连接已经断开。请跟服务器管理员联系】。 二、解决办法 2.0、前提 Windows Serv…

黑胶歌曲没权限,看我python大展神通,一分钟一个歌单

前言 大家早好、午好、晚好吖 ❤ ~ 人之初,喜白嫖。 大家都喜欢白嫖,我也喜欢,那么今天就来试试怎么白嫖抑云~ 一、需要的准备 1、环境 Python3.6以上 pycharm2019以上 2、模块 requests # 发送请求模块 第三方模块 exec js # 调用JS的…

CocosCreater 教程(下)

1.物理系统 1.1 2D刚体 刚体是组成物理世界的基本对象。 1.2 2D 碰撞组件 目前引擎支持三种不同的碰撞组件: 盒碰撞组件(BoxCollider2D)、圆形碰撞组件(CircleCollider2D) 和 多边形碰撞组件(PolygonCo…

Java中的抽象类和接口

java中的抽象类和接口抽象类什么是抽象类?抽象的使用场景抽象类的案例抽象类的特征、注意事项小结抽象类的应用知识:模版方法模式接口接口概述、特点接口的基本使用:被实现接口与接口的关系:多继承JDK8开始接口新增方法接口的注意…