【Golang】Go多线程中数据不一致问题解决方案--sync锁机制

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Go语言中的Sync锁
    • 一、互斥锁(Mutex)
      • 1.1 基本用法
      • 1.2 使用sync.WaitGroup等待一组Goroutine完成
      • 1.3 注意事项
    • 二、读写锁(RWMutex)
      • 2.1 基本用法
      • 2.2 注意事项
    • 三、Once(一次执行)
      • 3.1 基本用法
      • 3.2 注意事项
    • 四、总结

Go语言中的Sync锁

在Go语言的并发编程中,如何确保多个goroutine安全地访问共享资源是一个关键问题。Go语言提供了sync包,其中包含了多种同步原语,用于解决并发编程中的同步问题。本文将详细介绍sync包中的锁机制,并结合实际案例,帮助读者理解和使用这些锁。

要想解决临界资源安全的问题,很多编程语言的解决方案都是同步。
通过上锁的方式,某一时间段,只能允许一个goroutine来访问这个共享数据,当前goroutine访问完毕, 解锁后,其他的goroutine才 能来访问

我们可以借助于sync包下的锁操作。 synchronization

但是实际上,在Go的并发编程中有一句很经典的话:不要以共享内存的方式去通信:锁,而要以通信的方式去共享内存。

共享内存的方式
锁:多个线程拿的是同一个钥匙,go语言不建议使用锁机制来解决。不要以共享内存的方式去通信

而要以通信的方式去共享内存 go语言更建议我们使用 chan(通道) 来解决安全问题。(后面会学)

在Go语言中并不鼓励用锁保护共享状态的方式,在不同的Goroutine中分享信息(以共享内存的方式去通信)。
而是鼓励通过channeI将共享状态或共享状态的变化在各个Goroutine之间传递(以通信的方式去共享内存),这样同样能像用锁一样保证在同一的时间只有一个Goroutine访问共享状态。

当然,在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等。
Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言也差不多

一、互斥锁(Mutex)

互斥锁(sync.Mutex)是最基本的同步机制之一,用于确保同一时间只有一个goroutine能够访问特定的资源。
当一个goroutine持有互斥锁时,其他试图获取该锁的goroutine将会被阻塞,直到锁被释放。

1.1 基本用法

package mainimport ("fmt""sync""time"
)// 定义全局变量 票库存为10张
var tickets int = 10// 定义一个锁  Mutex 锁头
var mutex sync.Mutexfunc main() {go saleTicket("张三")go saleTicket("李四")go saleTicket("王五")go saleTicket("赵六")time.Sleep(time.Second * 5)
}// 售票函数
func saleTicket(name string) {for {// 在拿到共享资源之前先上锁mutex.Lock()if tickets > 0 {time.Sleep(time.Millisecond * 1)fmt.Println(name, "剩余票的数量为:", tickets)tickets--} else {// 票卖完,解锁mutex.Unlock()fmt.Println("票已售完")break}// 操作完毕后,解锁mutex.Unlock()}
}

上锁之后,就不会出现问题了
在这里插入图片描述

1.2 使用sync.WaitGroup等待一组Goroutine完成

sync.WaitGroup类型可以用来等待一组Goroutine完成。例如:

package mainimport ("fmt""sync""time"
)// waitgroup、var wg sync.WaitGroupfunc main() {// 公司最后关门的人   0// wg.Add(2) wg.Add(2)来告诉WaitGroup我们要等待两个Goroutine完成  开启几个协程,就add几个// wg.Done() 我告知我已经结束了  defer wg.Done()来在Goroutine完成时通知WaitGroup// 开启几个协程,就add几个wg.Add(2)go test1()go test2()fmt.Println("main等待ing")wg.Wait() // 等待 wg 归零,wg.Wait()来等待所有Goroutine完成 代码才会继续向下执行fmt.Println("end")// 理想状态:所有协程执行完毕之后,自动停止。//如果每次都强制设置个等待时间。那么协程代码也可能在这个时间内还没跑完,也可能提前就跑完了,所以设置死的等待时间不合理。此时就需要用到了等待组WaitGroup//time.Sleep(1 * time.Second)}
func test1() {for i := 0; i < 10; i++ {time.Sleep(1 * time.Second)fmt.Println("test1--", i)}wg.Done() //这里就将该代码块放在了其他逻辑之后
}
func test2() {defer wg.Done() // defer wg.Done()来在Goroutine完成时通知WaitGroup  如果不用defer就得把该方法放在其他代码之后for i := 0; i < 10; i++ {fmt.Println("test2--", i)}
}

主线程会等待所有协程执行完毕,才继续往下执行代码
在这里插入图片描述

1.3 注意事项

避免死锁:确保在获取锁之后,无论发生什么情况(包括panic),都能够释放锁。可以使用defer语句来确保锁的释放。
减少锁的持有时间:锁的持有时间越长,其他goroutine被阻塞的时间就越长,系统的并发性能就越差。因此,应该尽量减少锁的持有时间,只在必要的代码段中持有锁。
避免嵌套锁:尽量避免在一个锁已经持有的情况下再尝试获取另一个锁,这可能会导致死锁。

避免忘记调用Done:如果忘记调用Done方法,WaitGroup将会永远等待下去,导致程序无法正常结束。
避免负数计数器:调用Add方法时,如果传入的参数为负数,或者导致计数器变为负数,将会导致panic。

二、读写锁(RWMutex)

读写锁(sync.RWMutex)允许多个goroutine同时读取资源,但在写入时会阻塞所有其他读和写的goroutine。读写锁可以提高读多写少的场景下的并发性能。

2.1 基本用法

package mainimport ("fmt""sync"
)var (data map[string]intrwMu sync.RWMutex
)func readData(key string) int {rwMu.RLock()defer rwMu.RUnlock()return data[key]
}func writeData(key string, value int) {rwMu.Lock()defer rwMu.Unlock()data[key] = value
}func main() {data = make(map[string]int)var wg sync.WaitGroup// 写操作for i := 0; i < 10; i++ {wg.Add(1)go func(i int) {defer wg.Done()writeData(fmt.Sprintf("key%d", i), i*10)}(i)}// 读操作for i := 0; i < 100; i++ {wg.Add(1)go func(i int) {defer wg.Done()value := readData(fmt.Sprintf("key%d", i%10))fmt.Printf("Read: key%d = %d\n", i%10, value)}(i)}wg.Wait()
}

在这里插入图片描述

在上述代码中,我们定义了一个全局变量data和一个读写锁rwMu。readData函数用于读取data中的值,在读取之前先获取读锁,读取完成后释放读锁。
writeData函数用于写入data中的值,在写入之前先获取写锁,写入完成后释放写锁。
在main函数中,我们启动了10个写goroutine和100个读goroutine,分别调用writeData和readData函数。通过sync.WaitGroup等待所有goroutine完成。

2.2 注意事项

避免写锁长时间持有:写锁会阻塞所有其他读和写的goroutine,因此应该尽量减少写锁的持有时间。
读多写少场景:读写锁适用于读多写少的场景,如果写操作非常频繁,读写锁的性能优势可能会消失。
避免嵌套锁:与互斥锁类似,读写锁也应该避免嵌套使用。

三、Once(一次执行)

sync.Once用于确保某个操作只执行一次,无论有多少个goroutine调用它。这对于单例模式或初始化只执行一次的场景非常有用。

3.1 基本用法

package mainimport ("fmt""sync"
)var (once    sync.Oncemessage string
)func initMessage() {message = "Hello, World!"
}func printMessage() {once.Do(initMessage)fmt.Println(message)
}func main() {var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()printMessage()}()}wg.Wait()
}

在这里插入图片描述

在上述代码中,我们定义了一个全局变量message和一个sync.Once类型的变量once。
initMessage函数用于初始化message的值。printMessage函数通过once.Do方法确保initMessage只被调用一次,然后打印出message的值。
在main函数中,我们启动了10个goroutine,每个goroutine都调用printMessage函数。通过sync.WaitGroup等待所有goroutine完成。

3.2 注意事项

避免重复初始化:sync.Once确保某个操作只执行一次,因此它通常用于初始化全局变量或执行其他只需要执行一次的操作。
性能开销:虽然sync.Once的性能开销很小,但在高性能要求的场景下,仍然需要注意其使用。

四、总结

本文详细介绍了Go语言中sync包中的锁机制,包括互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、Once(一次执行)和WaitGroup(等待组)。
通过实际案例,帮助读者理解和使用这些锁。在并发编程中,正确地使用这些同步原语,可以确保多个goroutine安全地访问共享资源,避免数据竞争和其他并发问题。希望本文能够对大家有所帮助。

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

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

相关文章

怎样批量删除大量的QQ邮件?

当你的QQ邮箱中存在大量的邮件&#xff0c;手动删除的话&#xff0c;只能批量删除一页数据&#xff0c;显得很费力&#xff01;我教大家一个快速删除邮件的方法&#xff1a; 第一步&#xff1a;设置收信规则 第二步&#xff1a;利用收信规则&#xff0c;可将将收件箱中的文件…

C++:vector(题目篇)

文章目录 前言一、只出现一次的数字二、只出现一次的数字 II三、只出现一次的数字 III四、杨辉三角五、删除有序数组中的重复项六、数组中出现次数超过一半的数字七、电话号码的字母组合总结 前言 今天我们一起来看vector相关的题目~ 一、只出现一次的数字 只出现一次的数字…

echarts 中添加图片/图标

let myChart echarts.init(this.$refs.chartOne); // 注意这里的 ref 引用 myChart.setOption({ tooltip: {trigger: item,formatter: {b} : {c}},series: [{type: pie,radius: 50%,data: this.swjList,label: {formatter: (params) > {if (params.name ! ) {let percent…

程序设计基础I-实验7 函数(编程题)

7-1 sdut- C语言实验—计算表达式 计算下列表达式值&#xff1a; 输入格式: 输入x和n的值&#xff0c;其中x为非负实数&#xff0c;n为正整数。 输出格式: 输出f(x,n)&#xff0c;保留2位小数。 输入样例: 3 2输出样例: 在这里给出相应的输出。例如&#xff1a; 2.00 …

JUC高并发编程11:Fork/Join分支合并框架

1 Fork/Join 框架简介 Fork/Join 框架是 Java 7 引入的一种并行编程框架&#xff0c;用于将一个大任务拆分成多个小任务进行并行处理&#xff0c;最后将子任务的结果合并成最终的计算结果。Fork/Join 框架的核心思想是将任务递归地分解为更小的子任务&#xff0c;直到子任务足…

Zilliz获Forrester报告全球第一;OB支持向量能力;Azure发布DiskANN;阿里云PG发布内置分析引擎

重要更新 1. Azure发布PostgreSQL向量索引扩展DiskANN&#xff0c;声称在构建HNSW/IVFFlat索引上&#xff0c;速度、精准度都超越pg_vector&#xff0c;并解决了pg_vector长期存在的偶发性返回错误结果的问题( [1] )。 2. 阿里云RDS PostgreSQL 发布AP加速引擎&#xff08;rds…

Rust编程的函数

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 7.1 函 数 定 义 在Rust中&#xff0c;函数使用fn关键字定义&#xff0c;后跟函数…

干货资料速来领取!!

关于【中国ICD行业 PLM市场研究报告】: 作为电子产业的工业粮食,ICD(集成电路设计)是一个高度专业化的领域,当前已广泛应用于计算机、通信、消费电子、汽车、医疗设备等多个行业。 近年来,国家发布多项积极政策,助推行业快速发展来提升自主可控技术及国际市场竞争力,…

数字创意的孵化器:西安园区打造创意产业生态圈

在数字创意蓬勃发展的时代浪潮中&#xff0c;西安犹如一颗闪耀的新星&#xff0c;凭借着独特的园区建设&#xff0c;为数字创意产业注入无限活力。其中&#xff0c;西安数字创意孵化园区正发挥着不可替代的重要作用&#xff0c;尤其是西安国际数字影像产业园&#xff0c;更是成…

卡门涡街,大自然的诗意律动

1959年8月14日&#xff0c;人类拍摄了地球的首张卫星图像。半个多世纪后&#xff0c;地球已经被上万颗卫星环绕&#xff0c;传回的自拍也越来越清晰。2009年&#xff0c;美国宇航局对过去50年的地球卫星图进行评选&#xff0c;排第一名的是这一张&#xff0c;太平洋上的风流过阿…

面试还搞不懂redis,快看看这40道面试题

Redis 面试题 1、什么是 Redis?. 2、Redis 的数据类型&#xff1f; 3、使用 Redis 有哪些好处&#xff1f; 4、Redis 相比 Memcached 有哪些优势&#xff1f; 5、Memcache 与 Redis 的区别都有哪些&#xff1f; 6、Redis 是单进程单线程的&#xff1f; 7、一个字符串类…

食品研发PLM系统是什么?三品PLM食品行业解决方案详情介绍

在快速变化的食品行业中&#xff0c;企业面临着诸多挑战&#xff0c;特别是在产品研发管理方面。随着消费者对食品品质、健康、创新等方面需求的不断提升&#xff0c;食品企业必须在产品研发上不断创新&#xff0c;以满足市场需求。然而&#xff0c;这一过程中&#xff0c;食品…

数据分布过于集中 怎么办,python 人工智能 ,数据分析,机器学习pytorch tensorflow ,

数据分布过于集中&#xff0c;意味着数据的大部分值都聚集在某个特定区间内&#xff0c;这可能会导致统计分析的结果不够稳健&#xff0c;或者模型训练时出现过拟合等问题。针对这种情况&#xff0c;可以考虑以下几种方法来处理&#xff1a; 变换成 1. **数据转换**&#xff1…

服装生产管理的现代化:SpringBoot框架

2 关键技术简介 2.1 JAVA技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xff0…

ssm某物流企业管理信息系统-计算机毕业设计源码82788

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能分析 …

Windows下MYSQL8.0如何恢复root权限

误操作把root权限清掉导致数据库无法登录&#xff08;确实很难受&#xff09;&#xff0c;在网上找了很多方法&#xff0c;发现没有很行之有效的方法&#xff0c;在多方尝试终于找到了适合敏感宝宝体质的方法。 C:\Users\Administrator>mysql -u root -P3307 ERROR 1045 (2…

《征服数据结构》并查集(DSU)

摘要&#xff1a; 1&#xff0c;并查集的介绍 2&#xff0c;并查集的查找 3&#xff0c;并查集的合并 1&#xff0c;并查集的介绍 并查集(Disjoint-set data structure&#xff0c;不交集数据结构)是用于处理一些不交集的合并以及查询问题&#xff0c;它是非常重要的一种数据结…

【开源】RISC-V 修改neofetch中的Host描述

neofetch 介绍 neofetch 是一款用于在终端中显示系统信息的工具&#xff0c;其主要特点是以美观的方式展示宿主机的基本信息。它通常用于展示系统的分发版本、内核版本、硬件信息、桌面环境&#xff0c;以及一些个性化的设置&#xff0c;配合 ASCII 艺术风格的 logo&#xff0…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第三十章 文件IO和标准IO

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

【杭州马拉松:挑战自我,突破极限的征程】

2024年11月3日&#xff0c;杭州马拉松如约而至。这场挑战自我&#xff0c;突破极限的征程&#xff0c;将在杭州这座美丽的城市揭开序幕。对于广大跑步爱好者来说&#xff0c;如何选择合适的装备参加马拉松&#xff0c;成为了备战的重中之重。 在这里&#xff0c;我要向大家推荐…