深入解析 Golang 并发编程中的同步机制:WaitGroup 与 Mutex 详解

文章目录

      • 一、简介
      • 二、WaitGroup 的使用
        • 1. 什么是 WaitGroup?
        • 2. 基本操作
        • 3. WaitGroup 示例
        • 4. 注意事项
      • 三、Mutex 的使用
        • 1. 什么是 Mutex?
        • 2. 基本操作
        • 3. Mutex 示例
      • 四、竞争条件示例与解决
        • 1. 竞争条件问题示例
        • 2. 使用 Mutex 解决竞争条件
      • 五、使用 RWMutex 实现读写锁
        • RWMutex 示例
      • 六、小结


一、简介

在 Golang 的并发编程中,sync 包提供了一些基本的同步原语,用于解决多个 Goroutine 之间的协调与资源共享问题。常用的工具包括:

  • WaitGroup:用于等待一组 Goroutine 完成任务。
  • Mutex:用于保护共享资源,避免多个 Goroutine 同时修改数据导致竞争条件(Race Condition)。

本篇博客将详细介绍 WaitGroupMutex 的用法,并通过示例帮助初学者理解它们在实际场景中的应用。


二、WaitGroup 的使用

1. 什么是 WaitGroup?

WaitGroup 是一个计数器,用于等待多个 Goroutine 完成任务。它可以让主 Goroutine 阻塞,直到所有子 Goroutine 完成工作。

2. 基本操作
  • Add(delta int):添加 delta 个 Goroutine 到计数器。
  • Done():每个 Goroutine 在完成任务后调用,减少计数器的值。
  • Wait():阻塞调用者,直到计数器变为 0。
3. WaitGroup 示例
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // 任务完成时调用 Donefmt.Printf("Worker %d starting...\n", id)time.Sleep(time.Second) // 模拟任务执行fmt.Printf("Worker %d done\n", id)
}func main() {var wg sync.WaitGroup // 创建 WaitGroup 实例for i := 1; i <= 3; i++ {wg.Add(1) // 每启动一个 Goroutine,计数器加 1go worker(i, &wg)}wg.Wait() // 等待所有 Goroutine 完成fmt.Println("All workers done!")
}

输出:

Worker 1 starting...
Worker 2 starting...
Worker 3 starting...
Worker 1 done
Worker 2 done
Worker 3 done
All workers done!
4. 注意事项
  • 确保 Done 与 Add 匹配:每次 Add 对应一次 Done,否则可能会导致 Wait 永远阻塞。
  • 并发调用 Add:如果需要在运行时动态启动 Goroutine,应确保 Add 操作发生在 Wait 之前。

三、Mutex 的使用

1. 什么是 Mutex?

Mutex(互斥锁)用于防止多个 Goroutine 同时访问共享资源,从而避免竞争条件。只有一个 Goroutine 能够在同一时间持有 Mutex,其它 Goroutine 必须等待。

2. 基本操作
  • Lock():获取锁。如果锁已被其他 Goroutine 持有,则阻塞。
  • Unlock():释放锁。
3. Mutex 示例
package mainimport ("fmt""sync""time"
)var (counter int          // 共享变量mutex   sync.Mutex   // 互斥锁保护共享变量
)func increment(id int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 5; i++ {mutex.Lock()   // 获取锁counter++      // 修改共享变量fmt.Printf("Goroutine %d incremented counter to %d\n", id, counter)mutex.Unlock() // 释放锁time.Sleep(time.Millisecond * 100) // 模拟其他操作}
}func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1)go increment(i, &wg)}wg.Wait()fmt.Println("Final Counter Value:", counter)
}

输出(示例):

Goroutine 1 incremented counter to 1
Goroutine 2 incremented counter to 2
Goroutine 3 incremented counter to 3
...
Final Counter Value: 15

四、竞争条件示例与解决

1. 竞争条件问题示例

如果不使用 Mutex,多个 Goroutine 同时修改共享变量时会产生不一致的数据

package mainimport ("fmt""sync""time"
)var counter int // 共享变量func increment(wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 5; i++ {counter++time.Sleep(time.Millisecond * 100) // 模拟其他操作}
}func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1)go increment(&wg)}wg.Wait()fmt.Println("Final Counter Value:", counter)
}

输出:

Final Counter Value: 12

由于多个 Goroutine 竞争访问 counter,导致最终值不正确。


2. 使用 Mutex 解决竞争条件
mutex.Lock()
counter++
mutex.Unlock()

通过为每次修改操作加锁,确保同一时间只有一个 Goroutine 可以修改 counter,从而避免数据不一致。


五、使用 RWMutex 实现读写锁

RWMutex 是一种读写锁,允许多个 Goroutine 同时读取,但在写入时需要独占锁。

RWMutex 示例
package mainimport ("fmt""sync""time"
)var (data  = 0rwMux sync.RWMutex
)func readData(id int, wg *sync.WaitGroup) {defer wg.Done()rwMux.RLock() // 获取读锁fmt.Printf("Goroutine %d reading data: %d\n", id, data)rwMux.RUnlock() // 释放读锁
}func writeData(wg *sync.WaitGroup) {defer wg.Done()rwMux.Lock() // 获取写锁data++fmt.Printf("Writing data: %d\n", data)rwMux.Unlock() // 释放写锁
}func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1)go readData(i, &wg)}wg.Add(1)go writeData(&wg)wg.Wait()
}

六、小结

  1. WaitGroup 用于等待一组 Goroutine 完成任务,是 Goroutine 同步的常用工具。
  2. MutexRWMutex 用于保护共享资源,避免竞争条件。
  3. 使用锁时需注意避免死锁性能瓶颈

在下一篇博客中,我们将介绍 Golang 的 Context 包,并探讨如何在并发操作中控制超时和取消任务。敬请期待!

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

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

相关文章

FFMPEG录屏(19)--- 枚举Windows下的屏幕列表,并获取名称、缩略图

在Windows下枚举显示器列表并获取名称、缩略图 在Windows系统中&#xff0c;枚举显示器列表并获取它们的名称和缩略图是一个常见的需求。本文将详细介绍如何实现这一功能&#xff0c;涉及到的主要技术包括Windows API和C编程。 获取显示器信息 首先&#xff0c;我们需要一个…

爬虫结合项目实战

由于本人是大数据专业&#xff0c;所以准备的是使用pycharm工具进行爬虫爬取数据&#xff0c;然后实现一个可视化大屏 参考项目&#xff1a; 1.医院大数据可视化最后展示 2. 大数据分析可视化系统展示 代码包&#xff1a;

自由学习记录(13)

服务端常见的“资源” 在服务端&#xff0c;常见的“资源”指的是服务端提供给客户端访问、使用、处理或操作的各种数据和功能。根据不同类型的服务和应用场景&#xff0c;服务端的资源种类可以非常广泛。以下是一些常见的服务端资源类型&#xff1a; 1. 文件和静态资源 网页…

LSP的建立

MPLS需要为报文事先分配好标签&#xff0c;建立一条LSP&#xff0c;才能进行报文转发。LSP分为静态LSP和动态LSP两种。 静态LSP的建立 静态LSP是用户通过手工为各个转发等价类分配标签而建立的。由于静态LSP各节点上不能相互感知到整个LSP的情况&#xff0c;因此静态LSP是一个…

阿里云云盘在卸载时关联到PHP进程,如何在不影响PHP进程情况下卸载磁盘

1.问题&#xff1a; 在使用umount /dev/vdc1 卸载磁盘时&#xff0c;提示如下&#xff0c;导致无法在Linux系统下卸载磁盘 umount /dev/vdc1 umount: /var/www/html/*/eshop/IFile3: target is busy.(In some cases useful info about processes that usethe device is found…

NumPy学习Day18

1.数据迭代 1.1 nditer 使用np.nditer(x,order)遍历数组x中的元素 x为需要遍历的数组orderc’时按照行优先遍历orderf’时按照列优先遍历 1.2 flags参数 flags可以返回数组中元素的多维索引&#xff08;类似矩阵的坐标&#xff09; multi_index: 返回每个元素的多维索引。…

工具类的构造方法为什么要用private修饰

工具类&#xff08;Utility Class&#xff09;通常被设计为包含静态方法和静态变量的类&#xff0c;可以使用类名.方法名直接调用&#xff0c;不用进行实例化&#xff0c;这是工具类的设计原则&#xff0c;所以构造方法用private修饰&#xff08;因为公开的方法可以被实例化&am…

基于neo4j的糖尿病知识图谱数据

基于Neo4j的糖尿病知识图谱项目&#xff1a;毕业设计必备&#x1f4a1; 这个项目&#xff0c;专为需要深入挖掘医学或AI数据的朋友们量身定制&#xff0c;尤其适合用于毕业设计&#xff01;如果你对图谱构建、AI问答系统、或者正在学习Neo4j&#xff0c;那么你不得不看看这个技…

管家婆财贸ERP BB014.销售按库存选存货

最低适用版本: 财贸系列 22.8 插件简要功能说明: 销售按库存选存货插件,多元化价格跟踪体系用户根据存货+仓库自设仓库协议价仓库协议价支持手工或通过售价生成便捷录入销售单开单,无需选择客户,支持按存货查询库存余额及仓库协议价,选中存货即可将存货默认出库单位一级…

大厂物联网(IoT)高频面试题及参考答案

目录 解释物联网 (IoT) 的基本概念 物联网的主要组成部分有哪些? 描述物联网的基本架构。 IoT 与传统网络有什么区别? 物联网中常用的传感器类型有哪些? 描述物联网的三个主要层次。 简述物联网中数据安全的重要性 描述物联网安全的主要威胁 解释端到端加密在 IoT 中…

linux—基础命令及相关知识

1.0Linux的哲学思想&#xff08;优势&#xff09; 1、一切都是一个文件&#xff0c;一切硬件设备包括硬件接口都可以以文件形式显示 2、系统小型&#xff0c;轻量级&#xff0c;300个包&#xff08;不装桌面的情况下&#xff09; 3、避免令人困惑的用户界面&#xff08;图形…

在 Spring 中使用 @EhCache 注解作为缓存

文章目录 项目概况项目设置一个简单的 RESTful Web 服务Spring 整合 EhCache第 1 步&#xff1a;更新依赖项以使用 EhCache Spring 注解第 2 步&#xff1a;设置自定义缓存管理器第 3 步&#xff1a;配置 EhCache第 4 步&#xff1a;测试缓存 刷新缓存总结推荐阅读文章 EhCache…

第十六届蓝桥杯嵌入式真题

蓝桥杯嵌入式第十二届省赛真题二 蓝桥杯嵌入式第十三届省赛真题一 蓝桥杯嵌入式第十三届省赛真题二 蓝桥杯嵌入式第十四届省赛真题 蓝桥杯嵌入式第十四届模拟考试一 蓝桥杯嵌入式第十四届模拟考试二 蓝桥杯嵌入式第十五届模拟考试一 蓝桥杯嵌入式第十五届模拟考试二 蓝…

Linux系统基础-进程间通信(3)_模拟实现匿名管道

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Linux系统基础-进程间通信(3)_模拟实现匿名和命名管道 收录于专栏[Linux学习] 本专栏旨在分享学习Linux的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&a…

docker入门(三)自定义部署docker镜像

docker系列d​​​​​​​docker入门&#xff08;一&#xff09;安装及镜像命令_docker国内源-CSDN博客文章浏览阅读1.5k次&#xff0c;点赞44次&#xff0c;收藏12次。注意&#xff1a;是强依赖Linux环境&#xff0c;即便在windows上部署Docker其本质也都是先安装一个虚拟机&…

SpringBoot3.x和OCR构建车牌识别系统

本专题旨在展示 OCR 技术与 SpringBoot3.x 框架结合的广泛应用。我们会深入探讨它在医疗、金融、教育、交通、零售、公安等多个领域的现实应用。每个应用场景都会提供详细的实例、面临问题的分析与解决策略&#xff0c;以帮助您深入理解 OCR 技术在实践中的关键作用。让我们一同…

糖果——差分约束 + 正环判定及其优化(手搓栈 + 标记法)

题目 思考 这里转为判定负环可以是可以&#xff0c;但是不能用超级源点了&#xff08;改为把节点全部压入&#xff09;&#xff0c;因为按照题目条件&#xff0c;建立的应该是各个节点指向超级源点的有向边&#xff0c;这显然破坏了超级源点的功能 代码 #include <bits/st…

【数据结构与算法】Java中的基本数据结构:数组、链表、树、图、散列表等。

探索Java集合框架&#xff1a;数据结构的精髓与应用 摘要&#xff1a; 在本文中&#xff0c;我们将深入探讨Java集合框架中的核心数据结构&#xff0c;包括数组、链表、树、图、散列表、栈、队列、集合、映射和优先队列。通过分析每种数据结构的实现原理和特点&#xff0c;你将…

ArcGIS002:软件自定义设置

摘要&#xff1a;本文详细介绍安装arcgis10.2后软件自定义设置内容&#xff0c;包括工具条的启用、扩展模块的启用、如何加载项管理器、快捷键设置、样式管理器的使用以及软件常规设置。 一、工具条的启用 依次点击菜单栏【自定义】->【工具条】&#xff0c;根据工作需求勾…

某ai gpt的bug

某ai gpt的bug 背景 遇到了一个奇怪的现象&#xff1a; 输入内容 2024-10-21 10:09:31,052 ERROR o.a.j.t.JMeterThread: Test failed! java.lang.IllegalArgumentException:输出结果