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.Mapmap类型是并发安全的。要实现自己的并发安全需要借助``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系统可能在极端光照和高动态运动场景中捕获过曝或欠曝的图像,进而导致融合结果下降…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...
