Go 标准库源码分析 - sync 的 WaitGroup

2023-12-31 11:48

本文主要是介绍Go 标准库源码分析 - sync 的 WaitGroup,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

WaitGroup常用于多个goroutine协作,主要功能是阻塞等待一组goroutine完成。 

一、数据结构

type WaitGroup struct {noCopy noCopystate1 [3]uint32    // 用于存放任务计数器、等待者计数器和信号量 
}

WaitGroup采用64位的值来保存计数器,其中高32位为任务计数器,低32位为等待者计数器,另外用32位的值保存信号量。

WaitGroup在使用时需要64位的计数器进行原子操作,这要求计数器的地址是64位对齐的。如果是64位的操作系统,可直接取数组前两位元素作为计数器;如果是32位操作系统,state1可能不是64位对齐的地址,则将数组第一个元素作为信号量而后两个元素为计数器。

func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]} else {return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]}
}

 

二、使用方法

1. Add

func (wg *WaitGroup) Add(delta int) {// 获取计数器和信号量的地址statep, semap := wg.state()// 增加任务数,为原子操作state := atomic.AddUint64(statep, uint64(delta)<<32)// 任务计数器v := int32(state >> 32)// 等待者计数器w := uint32(state)// 任务数不能为负if v < 0 {panic("sync: negative WaitGroup counter")}// 在Add前执行Waitif w != 0 && delta > 0 && v == int32(delta) {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}if v > 0 || w == 0 {return}// 执行至此,说明任务数为0,且等待者数不为0// 原计数器与现在的不同,可能是Add和Wait同时执行if *statep != state {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 将计数器归零*statep = 0// 唤醒等待者for ; w != 0; w-- {runtime_Semrelease(semap, false, 0)}
}

2. Done

func (wg *WaitGroup) Done() {// 执行Add方法将任务数减1wg.Add(-1)
}

3. Wait

func (wg *WaitGroup) Wait() {// 获取计数器和信号量地址statep, semap := wg.state()for {// 原子读计数器的值state := atomic.LoadUint64(statep)// 任务计数器v := int32(state >> 32)// 等待计数器w := uint32(state)if v == 0 {// 任务数为0,直接返回return}// 任务数不为0时,原子增加等待者数if atomic.CompareAndSwapUint64(statep, state, state+1) {// 挂起当前goroutineruntime_Semacquire(semap)// 在Add方法里,唤醒等待者前会将计数器归零,因此若此处的statep不为0,则说明又调用了Add或Wait方法,导致panicif *statep != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}return}}
}

 

这篇关于Go 标准库源码分析 - sync 的 WaitGroup的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

Olingo分析和实践之EDM 辅助序列化器详解(最佳实践)

《Olingo分析和实践之EDM辅助序列化器详解(最佳实践)》EDM辅助序列化器是ApacheOlingoOData框架中无需完整EDM模型的智能序列化工具,通过运行时类型推断实现灵活数据转换,适用... 目录概念与定义什么是 EDM 辅助序列化器?核心概念设计目标核心特点1. EDM 信息可选2. 智能类

Olingo分析和实践之OData框架核心组件初始化(关键步骤)

《Olingo分析和实践之OData框架核心组件初始化(关键步骤)》ODataSpringBootService通过初始化OData实例和服务元数据,构建框架核心能力与数据模型结构,实现序列化、URI... 目录概述第一步:OData实例创建1.1 OData.newInstance() 详细分析1.1.1

Olingo分析和实践之ODataImpl详细分析(重要方法详解)

《Olingo分析和实践之ODataImpl详细分析(重要方法详解)》ODataImpl.java是ApacheOlingoOData框架的核心工厂类,负责创建序列化器、反序列化器和处理器等组件,... 目录概述主要职责类结构与继承关系核心功能分析1. 序列化器管理2. 反序列化器管理3. 处理器管理重要方

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

SpringBoot中六种批量更新Mysql的方式效率对比分析

《SpringBoot中六种批量更新Mysql的方式效率对比分析》文章比较了MySQL大数据量批量更新的多种方法,指出REPLACEINTO和ONDUPLICATEKEY效率最高但存在数据风险,MyB... 目录效率比较测试结构数据库初始化测试数据批量修改方案第一种 for第二种 case when第三种

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态