Go并发锁怎么用?读锁、写锁、互斥锁在使用上有什么区别?首次执行锁如何使用? 图文讲解 [_]~( ̄▽ ̄)~* Go相关

2023-05-16

文章目录

  • go锁
    • 互斥锁与读写互斥锁
      • 互斥锁
      • 读写互斥锁
    • 性能对比
    • 首次执行锁

go锁

如果一个公共变量同时被多个go程操控, 会带来一些不可预知的错误。就类似去看电影选座位, 同一个座位在同时只能有一个人使用, 一旦被人提前买走后就无法再次购买。

var (
	x   int64
	等待组 sync.WaitGroup
)

func() {
	for i := 0; i < 10000; i++ {
		x += 1
	}
	等待组.Done()
}

func main() {
	// 并发数越大越明显
	并发数量 := 3
	等待组.Add(并发数量)
	for i := 0; i < 并发数量; i++ {
		go()
	}
	等待组.Wait()
	fmt.Printf("当前数量: %v, (正常数量:%v )", x, 并发数量*10000)
}

在这里插入图片描述
多运行几次上述代码会发现当前数量进程会和正常数量对不上!
在这里插入图片描述
产生这种情况的原因很简单, 因为在处理全局变量的时候是需要时间的, 比如第一个go程拿到全局变量正在处理时, 第二个go程序去读取全局变量这时候读取到的值还是之前未经过第一个处理的值, 同样的问题也出现在保存的时候, 后保存的全局变量会覆盖掉前一个。

互斥锁与读写互斥锁

互斥锁

解决上述问题很简单, 只需要让全局变量同时只能交给一个Go程执行, 剩下的go程需要等待第一个执行结束后在执行即可。也就是保证同一时刻只有一个Go程能拿到全局变量。
在这里插入图片描述

互斥锁的实现理解起来较容易, 当锁定一个对象后, 其他Go程无论是想要读取还是写入, 都需要等待对象被解锁后才能执行。

var (
	x     int64
	等待组   sync.WaitGroup
	互斥锁   sync.Mutex
)

func() {
	for i := 0; i < 10000; i++ {
		// 加锁后, 并发的速度也会比常规的更快, 加个等待时间, 调高并发量就可以感知出来
		// time.Sleep(time.Millisecond)
		互斥锁.Lock()
		x += 1
		互斥锁.Unlock()
	}
	等待组.Done()
}

func main() {
	并发数量 := 3
	等待组.Add(并发数量)
	for i := 0; i < 并发数量; i++ {
		go()
	}
	等待组.Wait()
	fmt.Printf("当前数量: %v, (正常数量:%v )", x, 并发数量*10000)
}

使用互斥锁后无论运行多少次, 当前数量都会和正常数量相同

读写互斥锁

读写互斥锁的逻辑并不是读锁会锁定读操作, 写锁会锁定写操作。这样理解是错误的!

var (
	x     int64
	等待组   sync.WaitGroup
	读写互斥锁 sync.RWMutex
)

func() {
	for i := 0; i < 10000; i++ {
		// 这里使用读锁
		读写互斥锁.RLock()
		time.Sleep(time.Second)
		fmt.Println(x)
		x += 1
		读写互斥锁.RUnlock()
	}
	等待组.Done()
}

func main() {
	并发数量 := 3
	等待组.Add(并发数量)
	for i := 0; i < 并发数量; i++ {
		go()
	}
	等待组.Wait()
	fmt.Printf("当前数量: %v, (正常数量:%v )", x, 并发数量*10000)
}

上述代码中, 我使用了读锁, 并且在其中让其停滞了一秒, 但实际运行效果是下图这样
在这里插入图片描述
每次间隔一秒都会同时被读取三次(因为我的并发数是三), 按效果就可以看出, 读锁并没有对数据读取产生任何影响, 实际上读锁锁的是写入操作。

var (
	x     int64
	等待组   sync.WaitGroup
	互斥锁   sync.Mutex
	读写互斥锁 sync.RWMutex
)

func() {
	for i := 0; i < 10000; i++ {
		读写互斥锁.RLock()
		// 读写互斥锁.Lock()
		x += 1
		fmt.Println("+++++开始等待+++++", x)
		time.Sleep(time.Second)
		fmt.Println("-----结束等待-----", x)
		读写互斥锁.RUnlock()
		// 读写互斥锁.Unlock()
	}
	等待组.Done()
}

func_写锁() {
	time.Sleep(time.Second * 2)
	for i := 0; i < 10; i++ {
		读写互斥锁.Lock()
		x = 1
		fmt.Println("加_写锁", x)
		读写互斥锁.Unlock()
	}
}

func_读锁() {
	time.Sleep(time.Second * 2)
	for i := 0; i < 10; i++ {
		读写互斥锁.RLock()
		x = 1
		fmt.Println("加_读锁", x)
		读写互斥锁.RUnlock()
	}
}

func main() {
	并发数量 := 3
	等待组.Add(并发数量)
	for i := 0; i < 并发数量; i++ {
		go()
		// 可以分别注释写锁和读锁看看都会有什么效果
		//加_写锁()_读锁()
	}
	等待组.Wait()
	fmt.Printf("当前数量: %v, (正常数量:%v )", x, 并发数量*10000)
}

在这里插入图片描述
根据上面的代码的输出结果可以看出使用写锁的函数只有在读锁解除后(结束等待后)才会开始执行写入, 而使用读锁的函数并不会被读锁阻塞在读锁还没有被解除的时候就执行了。同样的方法,我们只需把上述加()函数中的读锁替换成写锁就可以测试写锁了。最后我们可以获得如下结论:
读锁会阻塞写锁, 但不会对读锁产生影响; 写锁既会阻塞写如, 还会阻塞读取。并且写操作的优先级是高于读的, 如果在多个读取被阻塞的时候出现一个写操作, 写操作会被优先唤醒!
注: 读写互斥锁.RLocker().Lock()读写互斥锁.RLocker().Unlock()相当于读写互斥锁.Lock()读写互斥锁.Unlock()的别名。
想要知道Go是如何实现这样的读写互斥锁可以看一下这个大佬的博客 GO 读写锁实现原理剖析

性能对比

读多写少的情况下, 读写锁性能明显优于互斥锁, 写入约接近读取时性能差距越小, 当写入多于读取的时候性能会逐渐持平。具体性能对比方法可以看李文周大佬的博客。

首次执行锁

在项目使用并发的时候, 往往出现某些初始化只需要执行一次即可, 这时候就可以利用到sync.Once()类中的Do(函数)方法, 这个方法能确保当前函数只被执行了一次!

var 等待组 sync.WaitGroup
var 首次执行 = sync.Once{}

func 初始化() {
	fmt.Println("--------------初始化--------------")
	开始初始化 := func() {
		fmt.Println("初始化成功")
	}
	首次执行.Do(开始初始化)
	defer 等待组.Done()
}

func main(){
	for i := 0; i < 3; i++ {
		等待组.Add(1)
		go 初始化()
	}
	等待组.Wait()
}

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go并发锁怎么用?读锁、写锁、互斥锁在使用上有什么区别?首次执行锁如何使用? 图文讲解 [_]~( ̄▽ ̄)~* Go相关 的相关文章

  • 【px4编译】make px4fmu-v3_default upload 不能使用

    今天遇到编译pixhawk2代固件无法upload的问题 xff0c 出现TypeError a bytes like object is required not str xff0c 在1代中是可以make upload的 ninja E
  • 【PX4代码】关于px4代码中timestamp与gps_itow的时间问题

    gps的输出频率为5hz xff0c 由此可见timestamp应该是113248090 1e 6转换为s xff0c 由次可见gps的itow时间应该为271998999 1e 3转换为s 总结 px4中的时间为cpu应该除以10 6 x
  • 【PX4代码】关于ekf2输出频率只有1ohz的问题

    问题 我在使用的是pixhawk 2代 imu的输出频率都在100hz以上 xff0c 使用的gps模块输出频率为5hz xff0c 使用log的local position csv 查看ekf2估计输出的点位信息只有10hz xff0c
  • Keil4中C51的debug调试步骤技巧

    1 选择相应的调试仿真连接器 xff1a 选择 xff1a project gt Options for Target 如下图所示 xff1a 然后在弹出对话框的Debug选项下选择仿真器的型号 xff1a 2 进入调试 xff1a 3 部
  • 怎么操作linux服务器

    Linux 服务器就是采用 Linux 系统的网络服务器 xff0c 同时也有采用 windows 的服务器 xff0c 作用是类似的 而 Linux 并不是一个特定的系统 xff0c 而是使用 Linux 内核的系统 xff0c 现在发行
  • 人工智能如何可以思考?

    近日在给同事讲人工智能的时候 xff0c 提到当数据量不够的时候 xff0c 必要时需要加入人工工程 xff0c 引导计算机 归纳 一些知识 xff0c 毕竟计算机智能比起人类智能 xff0c 最大的缺陷可能在于不懂得 举一反三 换句话说
  • bag文件内topic对应的frame_id查看指令

    启动ROS roscore 运行数据集 span class token comment 数据集小的话 xff0c 建议慢速播放 span rosbag play xxx span class token punctuation span
  • GPS数据类型(ROS)

    文章目录 一 传感器分类二 作用三 系统组成四 位置表示五 数据格式六 ROS中GPS数据格式sensor msgs NavSatFixROS中GPS数据主要包含这四类 xff0c 分别是gps 裸数据ros封装 xff0c 位置 xff0
  • cmake使用教程

    CMakeLists txt文档编写以及packsge xml文档介绍 CMakeLists txt文档一 cmake minimum required命令二 CMake中的编译类型三 cmake编译选项 xff08 与2对应 xff09
  • 四旋翼无人机飞控系统设计(闭环控制系统)

    对于一个简单的飞控程序来说 xff0c 控制器是它最核心的部分 xff0c 这里主要与大家讨论控制系统的基本理论知识 xff08 自控大佬请绕道 xff09 xff0c 包括控制系统概念 闭环控制系统的原理 下篇将侧重包含pid控制算法的具
  • 针对frame_id和child_frame_id的理解

    ros基础必看之各个frame的理解 ROS坐标系统 xff0c 常见的坐标系和其含义 ROS中TF 坐标系转换 原理与使用
  • IMU预积分学习

    IMU预积分学习 一 IMU状态传递方程 一 IMU状态传递方程 几种不同的表达形式 xff1a 1 lio mapping xff1a 参考大佬 xff1a lio mapping 及 VINS Mono代码及理论推导 xff08 2 x
  • Python3 内置模块 - os

    方法名说明os access判断文件权限os chdir改变当前工作目录os chmod file 修改文件权限os execvp 启动一个新进程os execvp 执行外部程序脚本 xff08 Uinx xff09 os fork 获取父
  • AttributeError: module 'tornado.web' has no attribute 'asynchronous'解决方法

    AttributeError module tornado web has no attribute 39 asynchronous 解决方法 今天看tornado异步时发现的错误 xff0c 查了一下 xff0c 原来tornado6以后
  • 【STM32项目】- 人体检测(体温、心率、心跳、跌倒检测)

    STM32人体检测 xff08 体温 心率 心跳 跌倒检测 xff09 43 zigbee 笔者前言 在闲鱼有缘结识的一个哥们 xff0c 帮助做的基于STM32人体检测系统 xff0c 我负责硬件程序开发设计 xff0c 哥们负责客户端服
  • ESP32 之 esp32-cam wifi拍照传图系统1

    文章目录 ESP32 之 esp32 cam wifi拍照传图系统1 效果演示2 材料准备3 原理图接线4 下载代码 ESP32 之 esp32 cam wifi拍照传图系统 1 效果演示 2 材料准备 ESP32 camUSB转TTL按钮
  • ESP32 之 esp32-cam wifi拍照传图系统2

    ESP32 之 esp32 cam wifi拍照传图系统 补充说明 ESP32 CAM总结 一 ESP32初识 ESP32 CAM模组的核心芯片 xff1a ESP32 S 模块是一款超小体积的多功能通用型 802 11b g n WiFi
  • 【PX4 飞控二次开发】第一个程序 打印输出-模拟

    第一个程序 1 编写任务代码 Cmake文件2 编译 1 编写任务代码 Cmake文件 span class token operator span span class token operator span PIX4 span clas
  • 【PX4 飞控二次开发】UORB 发布及订阅自定义

    UORB测试 一 添加msg1 创建 msg文件2 Cmakelists txt修改3 编译错误4 编译成功 二 发布及订阅主题1 添加代码2 修改cmake3 再次编译 一 添加msg 1 创建 msg文件 span class toke
  • 【PX4 飞控二次开发】自制ESP8266WIFI数传

    ESP8266数传模块 一 简介二 下载烧录固件1 下载MavLink ESP8266固件2 烧录 三 连线四 配置ESP8266五 连接WIFI Bridge 一 简介 ESP8266是一款低成本 xff0c 易于使用的Wi Fi模块 x

随机推荐