Go语言的并发安全与互斥锁
线程通讯
在程序中不可避免的出现并发或者并行,一般来说对于一个程序大多数是遵循开发语言的启动顺序。例如,对于go语言来说,一般入口为main
,main中依次导入import
导入的包,并按顺序执行init
方法,之后在按照调用顺序执行程序。所以一般情况下程序是串行的。如下所示:
在很多时候串行并不满足要求,程序同时需要满足,很多客户访问,例如web程序必须要设置为并发的才能满足众多请求。
go语言通过go
关键字实现新线程,如下·:
go func(){fmt.Println("-------开启一个新线程-----")
}()
线程通讯,go语言实现线程通讯是通过进程通讯实现内存共享。go语言内置chan
数据结构实现线程通讯与数据共享。
package mainimport ("fmt""sync""time"
)var ch = make(chan int)
var wati sync.WaitGroupfunc main() {wati.Add(2)go producter()go customer()wati.Wait()
}func producter() {for i := 0; i < 10; i++ {ch <- i * 10fmt.Println("-----等待1秒")time.Sleep(time.Second * 1)}wati.Done()close(ch)
}
func customer() {for {x, ok := <-chfmt.Println("-----------------custome", x, ok)if ok {fmt.Println("-----", x)} else {fmt.Println("----no data")break}}wati.Done()
}
chan本身在设计上是并发安全的。这意味着多个协程可以同时安全地对一个chan进行发送和接收操作,而无需额外的同步措施。Go 语言的运行时系统会自动处理这些操作的并发安全性,保证数据的正确传递和协程的正确同步。
线程等待
在多线程中,各个线程是是独立的,并不知道线程完成的次序,所以线程等待也很重要,如下代码:
package mainimport ("os""sync"
)var Filestream = make(chan []byte)func main() {var str = []byte("hello")go Read(str)go Write()
}func Read(by []byte) {Filestream <- byclose(Filestream)
}func Write() error {file, err := os.Create("test.txt")if err != nil {return err}by := <-Filestream_, err = file.Write(by)if err != nil {return err}return nil
}
上述代码通过chan
实现了多线程创建文件,但是实际上执行代码并不能成功,这是由于主线程没有等待其他线程,导致主线程在过早结束,程序结束,通过sync.WaitGroup
库实现线程等待,改造后的代码如下:
package mainimport ("os""sync"
)var Filestream = make(chan []byte)
var wait sync.WaitGroupfunc main() {var str = []byte("hello")wait.Add(2)go Read(str)go Write()wait.Wait()
}func Read(by []byte) {Filestream <- byclose(Filestream)wait.Done()
}func Write() error {file, err := os.Create("test.txt")if err != nil {return err}by := <-Filestream_, err = file.Write(by)if err != nil {return err}wait.Done()return nil
}
并发安全
数据锁
在并发时就会出现资源被竞争的情况,这就是涉及到并发安全了,例如对于一个数组var list []string
在对这个数组插入数据时,对于同一时刻进行的操作数据就可能会丢失,因此在操作数据时一定要保证操作的数据结构是并发安全的,例如sync.Map
就是线程安全的go语言底层实现了。
那么如何对自定义的数据结构体实现并发安全呢,就要用到互斥锁了。互斥锁是一种用于多线程或多协程编程中的同步原语,其主要目的是保护共享资源,防止多个线程或协程同时访问和修改这些资源,从而避免数据竞争和并发冲突。
在并发编程中,多个线程或协程可能会同时访问和修改同一个共享变量。如果不加以控制,就可能导致数据竞争,即多个操作同时对同一个数据进行读写,从而导致数据的不一致性或错误结果。
互斥锁通过提供一种互斥访问的机制,确保在任何时刻只有一个线程或协程能够访问被保护的共享资源。当一个线程或协程获取了互斥锁后,其他试图获取该锁的线程或协程就会被阻塞,直到锁被释放。
Go语言的互斥锁被sync.Mutex
实现。在go语言中提供了sync.Map
map类型是并发安全的。要实现自己的并发安全需要借助``sync.Mutex`如下:
- 并发安装的结构体
type safeArr[T any] struct {sync.Mutexdata []T
}func (self *safeArr[T]) Add(item T) {self.Lock()self.data = append(self.data, item)self.Unlock()
}func (self *safeArr[T]) Remove(index int) {self.Lock()if index >= 0 && index < len(self.data) {self.data = append(self.data[:index], self.data[index+1:]...)}self.Unlock()
}func (self *safeArr[T]) Data() []T {self.Lock()self.Unlock()return self.data
}
注意,data 是不能直接向外部暴露的,不可以使用
append
直接操作,必须通过并发安装的Add
方法。
- 测试
var arr safeArr[string]func Test(c *fiber.Ctx) error {arr.Add("sss")return c.JSON(fiber.Map{"data": arr.data,})
}func main() {app := fiber.New()app.Get("/test", Test)go func() {for {if requestCount == 0 {wg.Done()}}}()app.Listen(":8081")
}
通过上述代码测试代码测试不用路由请求进来后的线程对并发安全的
safeArr
的处理是否满足要求。如下所示:
同步的路由线程进来之后,对全局的变量可以实现操作,这里并不能体现它是并发安全的,我们将Add
方法的self.Unlock()
注释掉,再次启动服务,请求接口如下:
如上图所示,在注释掉解锁代码后,再次请求会一直堵塞等待解锁,可以看出上述定义的结构体就是并发安全的了。
互斥锁会使线程变为阻塞线程,等待解锁,而不是直接停掉。
方法锁
如何实现对方法枷锁,同一时刻只允许一个线程使用该方法。对方法加锁和对数据结构加锁一样,再方法内部加锁。
var arr []stringvar wu sync.Mutexfunc Test(c *fiber.Ctx) error {wu.Lock()defer wu.Unlock()arr = append(arr, "ssss")return c.JSON(fiber.Map{"data": arr,})
}func main() {app := fiber.New()app.Get("/test", Test)go func() {for {if requestCount == 0 {wg.Done()}}}()app.Listen(":8081")
}
如果将defer wu.Unlock()
注释掉,也会是线程进入等待状态,如下:
但是又会有新的问题,由于线程是独立的,此时存在一个线程处于阻塞状态,但是却可以再次发送新的请求,再阻塞的这段时间内,可以一直发送请求,多次请求会也会造成数据的错误,如下所示:
如上图可以发现,互斥锁可以是方法变成并发安全的,但是在线程等待的过程中仍然可以发送请求。
Redis互斥锁
在上一节的方法互斥锁中,需要额外的需求,就是如果当前的线程占用某个资源时,新的线程不会处于阻塞状态而是直接停止,这时就需要有外部的标识记录资源的占用情况,就需要借助r内存数据库如redis了。如下:
- redis初始化
var lock sync.Mutexvar Client *CustomRedisClienttype CustomRedisClient struct {redis.Conn
}func Init() {conn, err := redis.Dial("tcp", config.RedisVar.Host)if err != nil {panic(errors.New("conn redis failed"))}_, err = conn.Do("auth", config.RedisVar.Password)if err != nil {panic(errors.New("redis auth failed"))}c := CustomRedisClient{conn}Client = &cfmt.Println("conn redis success")
}func (c *CustomRedisClient) Cmd(commandName string, args ...interface{}) (reply interface{}, err error) {lock.Lock()defer lock.Unlock()return c.Do(commandName, args...)
}
- redis互斥锁
func Lock(name string) (error, *string) {apply, err := Client.Cmd("GET", name)if apply != nil || err != nil {return errors.New("locking"), nil}uid := utils.UUID()fmt.Printf("name: %v, uid: %v \n", name, uid)_, err = Client.Cmd("SET", name, uid, "EX", 5)if err != nil {Client.Cmd("DEL", name)return errors.New("redis set err"), nil}return nil, &uid
}func Unlock(name string, uid string) error {reply, err := Client.Cmd("GET", name)if reply == nil && err == nil {return nil}s := string(reply.([]byte))if s != uid {Client.Cmd("DEL", name)return errors.New("can not unlock")}_, err = Client.Cmd("DEL", name)if err != nil {Client.Cmd("DEL", name)return err}return nil
}
使用上述的redis互斥锁就可以显示,当某个资源被线程占用时,另一个资源进来会直接停掉。
对代码改造使用新的互斥锁,如下:
func Test(c *fiber.Ctx) error {err, s := redis_util.Lock("test")if err != nil {if errors.Is(err, errors.New("locking")) {return c.JSON(fiber.Map{"data": "locking",})} else {return c.JSON(fiber.Map{"data": err.Error(),})}}defer redis_util.Unlock("test", *s)time.Sleep(time.Second * 15)arr = append(arr, "ssss")return c.JSON(fiber.Map{"data": arr,})
}
如上图所示,当资源被占用是会返回locking
,需要注意的是redis存储的互斥标识的时间一定要大于或等于程序的执行时间,不然程序还未执行玩,redis占用标识就销毁了,导致错误。
相关文章:

Go语言的并发安全与互斥锁
线程通讯 在程序中不可避免的出现并发或者并行,一般来说对于一个程序大多数是遵循开发语言的启动顺序。例如,对于go语言来说,一般入口为main,main中依次导入import导入的包,并按顺序执行init方法,之后在按…...

SpringBoot框架在资产管理中的应用
3系统分析 3.1可行性分析 通过对本企业资产管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本企业资产管理系统采用Spring Boot框架,JAVA作…...

ElasticSearch备考 -- 集群配置常见问题
一、集群开启xpack安全配置后无法启动 在配置文件中增加 xpack.security.enabled: true 后无法启动,日志中提示如下 Transport SSL must be enabled if security is enabled. Please set [xpack.security.transport.ssl.enabled] to [true] or disable security b…...

【UE5】一种老派的假反射做法,可以用于移动端,或对反射的速度、清晰度有需求的地方
没想到大家这篇文章呼声还挺高 这篇文章是对它的详细实现,建议在阅读本篇之前,先浏览一下前面的文章,以便更好地理解和掌握内容。 这种老派的假反射技术,适合用于移动端或对反射效果的速度和清晰度有较高要求的场合。该技术通过一…...

FasterNet中Pconv的实现、效果与作用分析
发表时间:2023年3月7日 论文地址:https://arxiv.org/abs/2303.03667 项目地址:https://github.com/JierunChen/FasterNet FasterNet-t0在GPU、CPU和ARM处理器上分别比MobileViT-XXS快2.8、3.3和2.4,而准确率要高2.9%。我们的大型…...
QToolbar工具栏下拉菜单不弹出有小箭头
这里说了怎么弹出:Qt 工具栏QToolBar添加带有弹出菜单的QAction_qt如何将action添加到工具栏-CSDN博客 然后如果你是在UI里面建立的action,并拖到了toolbar,并在代码中设置菜单,例如: ui->mytoolbar->setMenu(…...

w025基于SpringBoot网上超市的设计与实现
🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文件࿰…...

深度学习在推荐系统中的应用
参考自《深度学习推荐系统》,用于学习和记录。 前言 (1)与传统的机器学习模型相比,深度学习模型的表达能力更强,能够挖掘(2)深度学习的模型结构非常灵活,能够根据业务场景和数据特…...
软考系统架构设计师论文:论面向对象的建模及应用
试题三 论面向对象的建模及应用 软件系统建模是软件开发中的重要环节,通过构建软件系统模型可以帮助系统开发人员理解系统、抽取业务过程和管理系统的复杂性,也可以方便各类人员之间的交流。软件系统建模是在系统需求分析和系统实现之间架起的一座桥梁,系统开发人员按照软件…...

LSM-TREE和SSTable
一、什么是LSM-TREE LSM Tree 是一种高效的写优化数据结构,专门用于处理大量写入操作 在一些写多读少的场景,为了加快写磁盘的速度,提出使用日志文件追加顺序写,加快写的速度,减少随机读写。但是日志文件只能遍历查询…...
mysql 升级
# 备份数据库数据 mysqldump -u root -p --single-transaction --all-databases > backup20240830.sql; # 备份mysql数据目录: cp -r /data/mysql mysql20240902 # 备份mysql配置文件my.cnf cp -r /etc/my.cnf my.cnf20240902 systemctl stop mysqld tar -x…...

基于Multisim定时器倒计时器电路0-999计时计数(含仿真和报告)
【全套资料.zip】定时器倒计时器电路Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.0-999秒定时功能,计时间隔1秒,数字显示。 2. 进行0-999秒减计时,…...
力扣11.5
1035. 不相交的线 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足: nums1[i] nums2[j]且绘制的直线不与任何其他连线(非…...

arkUI:层叠布局(Stack)
arkUI:层叠布局(Stack) 1 主要内容说明2 相关内容2.1 层叠布局(Stack)2.1.1 源码1的相关说明2.1.2 源码1 (层叠布局)2.1.3 源码1运行效果2.1.3.1 当alignContent: Alignment.Bottom2.1.3.2 当al…...
【LeetCode】【算法】221. 最大正方形
LeetCode 221. 最大正方形 题目描述 在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。 思路 思路:动态规划。初始化时,第0列和第0行,若nums[i][j]1则dp[i][j]初始化为1&am…...
怎麼解除IP阻止和封禁?
IP地址被阻止的原因 安全問題如果有人使用 IP 地址試圖侵入某個網站或導致其他安全問題,則可能會禁止該 IP 以保護該網站。濫用或垃圾郵件如果IP地址發送過多垃圾郵件、發佈不當內容或濫用網站服務,則可能會被禁止,以保持網站清潔和友好。違…...

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈
O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈 O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈O-RAN前端O-RAN 前传平面C-Plane(控制平面):控制平面消息定义数据传输、波束形成等所需的调度、协调。U-Plane(用户平面)&#…...

一招解决Mac没有剪切板历史记录的问题
使用Mac的朋友肯定都为Mac的剪切功能苦恼过,旧内容覆盖新内容,导致如果有内容需要重复输入的话,就需要一次一次的重复复制粘贴,非常麻烦 但其实Mac也能够有剪切板历史记录功能,iCopy,让你的Mac也能拥有剪切…...

Node-Red二次开发:各目录结构说明及开发流程
node-red下载之前需要安装nodejs软件,然后设置环境变量; node-red下载之后,需要先安装依赖: 1. 安装依赖shell npm install # 或 yarn install 2. 运行shell npm run dev node-red的目录结构: node-red的前后端都是…...

论文阅读-Event-based Visible and Infrared Fusion via Multi-task Collaboration
一、前言 可见光图像与红外图像融合(VIF)通过结合热红外图像与可见光图像的丰富纹理,提供了一个全面可靠的场景描述。然而,传统的VIF系统可能在极端光照和高动态运动场景中捕获过曝或欠曝的图像,进而导致融合结果下降…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...