当前位置: 首页 > news >正文

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语言的并发安全与互斥锁

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

SpringBoot框架在资产管理中的应用

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

ElasticSearch备考 -- 集群配置常见问题

一、集群开启xpack安全配置后无法启动 在配置文件中增加 xpack.security.enabled: true 后无法启动&#xff0c;日志中提示如下 Transport SSL must be enabled if security is enabled. Please set [xpack.security.transport.ssl.enabled] to [true] or disable security b…...

【UE5】一种老派的假反射做法,可以用于移动端,或对反射的速度、清晰度有需求的地方

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

FasterNet中Pconv的实现、效果与作用分析

发表时间&#xff1a;2023年3月7日 论文地址&#xff1a;https://arxiv.org/abs/2303.03667 项目地址&#xff1a;https://github.com/JierunChen/FasterNet FasterNet-t0在GPU、CPU和ARM处理器上分别比MobileViT-XXS快2.8、3.3和2.4&#xff0c;而准确率要高2.9%。我们的大型…...

QToolbar工具栏下拉菜单不弹出有小箭头

这里说了怎么弹出&#xff1a;Qt 工具栏QToolBar添加带有弹出菜单的QAction_qt如何将action添加到工具栏-CSDN博客 然后如果你是在UI里面建立的action&#xff0c;并拖到了toolbar&#xff0c;并在代码中设置菜单&#xff0c;例如&#xff1a; ui->mytoolbar->setMenu(…...

w025基于SpringBoot网上超市的设计与实现

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0…...

深度学习在推荐系统中的应用

参考自《深度学习推荐系统》&#xff0c;用于学习和记录。 前言 &#xff08;1&#xff09;与传统的机器学习模型相比&#xff0c;深度学习模型的表达能力更强&#xff0c;能够挖掘&#xff08;2&#xff09;深度学习的模型结构非常灵活&#xff0c;能够根据业务场景和数据特…...

软考系统架构设计师论文:论面向对象的建模及应用

试题三 论面向对象的建模及应用 软件系统建模是软件开发中的重要环节,通过构建软件系统模型可以帮助系统开发人员理解系统、抽取业务过程和管理系统的复杂性,也可以方便各类人员之间的交流。软件系统建模是在系统需求分析和系统实现之间架起的一座桥梁,系统开发人员按照软件…...

LSM-TREE和SSTable

一、什么是LSM-TREE LSM Tree 是一种高效的写优化数据结构&#xff0c;专门用于处理大量写入操作 在一些写多读少的场景&#xff0c;为了加快写磁盘的速度&#xff0c;提出使用日志文件追加顺序写&#xff0c;加快写的速度&#xff0c;减少随机读写。但是日志文件只能遍历查询…...

mysql 升级

# 备份数据库数据 mysqldump -u root -p --single-transaction --all-databases > backup20240830.sql; # 备份mysql数据目录&#xff1a; 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秒定时功能&#xff0c;计时间隔1秒&#xff0c;数字显示。 2. 进行0-999秒减计时&#xff0c…...

力扣11.5

1035. 不相交的线 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非…...

arkUI:层叠布局(Stack)

arkUI&#xff1a;层叠布局&#xff08;Stack&#xff09; 1 主要内容说明2 相关内容2.1 层叠布局&#xff08;Stack&#xff09;2.1.1 源码1的相关说明2.1.2 源码1 &#xff08;层叠布局&#xff09;2.1.3 源码1运行效果2.1.3.1 当alignContent: Alignment.Bottom2.1.3.2 当al…...

【LeetCode】【算法】221. 最大正方形

LeetCode 221. 最大正方形 题目描述 在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内&#xff0c;找到只包含 ‘1’ 的最大正方形&#xff0c;并返回其面积。 思路 思路&#xff1a;动态规划。初始化时&#xff0c;第0列和第0行&#xff0c;若nums[i][j]1则dp[i][j]初始化为1&am…...

怎麼解除IP阻止和封禁?

IP地址被阻止的原因 安全問題如果有人使用 IP 地址試圖侵入某個網站或導致其他安全問題&#xff0c;則可能會禁止該 IP 以保護該網站。濫用或垃圾郵件如果IP地址發送過多垃圾郵件、發佈不當內容或濫用網站服務&#xff0c;則可能會被禁止&#xff0c;以保持網站清潔和友好。違…...

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈

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

一招解决Mac没有剪切板历史记录的问题

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

Node-Red二次开发:各目录结构说明及开发流程

node-red下载之前需要安装nodejs软件&#xff0c;然后设置环境变量&#xff1b; node-red下载之后&#xff0c;需要先安装依赖&#xff1a; 1. 安装依赖shell npm install # 或 yarn install 2. 运行shell npm run dev node-red的目录结构&#xff1a; node-red的前后端都是…...

论文阅读-Event-based Visible and Infrared Fusion via Multi-task Collaboration

一、前言 可见光图像与红外图像融合&#xff08;VIF&#xff09;通过结合热红外图像与可见光图像的丰富纹理&#xff0c;提供了一个全面可靠的场景描述。然而&#xff0c;传统的VIF系统可能在极端光照和高动态运动场景中捕获过曝或欠曝的图像&#xff0c;进而导致融合结果下降…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...