Go 之 sync.Mutex 加锁失效现象

devtools/2024/9/24 10:20:39/

我先声明一下,并不是真的加锁失效,而是我之前的理解有误,导致看起来像是加锁失效一样。于是乎记录一下,加深一下印象。

我之前有个理解误区(不知道大家有没有,有的话赶紧纠正一下——其实也是因为我这块的知识掌握不牢固导致的):觉得只要是加锁后,在我主动调用解锁之前,这个块范围内的变量一定不会被其他地方修改。后来验证发现,我大错特错了。

起因

最近在学习 sync.Mutex 加锁时,写了下面一段代码进行练习。

package mainimport ("fmt""sync""time"
)type Info struct {mu sync.MutexValue string
}func Update(info *Info) {fmt.Printf("%s: before update. Value: %s\n", time.Now().Format(timeFormat), info.Value)info.mu.Lock()defer info.mu.Unlock()time.Sleep(2 * time.Second)fmt.Printf("%s: in update. Value: %s\n", time.Now().Format(timeFormat), info.Value)info.Value = "update"fmt.Printf("%s: after update. Value: %s\n", time.Now().Format(timeFormat), info.Value)
}const timeFormat = "2006-01-02 15:04:05"func main() {fmt.Printf("%s: main start\n", time.Now().Format(timeFormat))info := &Info{}var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()Update(info)}()time.Sleep(time.Second)info.Value = "main"fmt.Printf("%s: in main. Value: %s\n", time.Now().Format(timeFormat), info.Value)wg.Wait()
}

按照我原先上面的理解,Update() 函数当中,before update 和 in update 中对应结构体的值应该是不会变的(毕竟我加了锁)。然而从运行结果发现,Update() 函数执行期间,结构体变量的 Value 竟然还是被外部主线程修改了。

分析

那么为什么会这样呢?明明 Update() 里边已经添加了锁,为什么执行期间还是会被其他地方修改呢?

最后发现,究其原因,还是在于主线程修改变量的值的时候,没有先判断锁 mu 是否已经释放,就直接进行了修改操作。

主线程中加上获取锁的操作后,会先判断当前锁是否被释放,如果没被释放,就会一直进行等待直到锁释放后才继续执行后面的操作。

输出结果也和预期保持一致 。

2024-04-16 23:59:13: main start
2024-04-16 23:59:13: before update. Value: 
2024-04-16 23:59:15: in update. Value: 
2024-04-16 23:59:15: after update. Value: update
2024-04-16 23:59:15: in main. Value: main

正常来说,锁是要配合多 goroutine 来使用的, 对于单线程来说,由于没有其他线程进行资源竞争,加锁的意义不大;对于多 goroutine 而言,对于获取和释放锁的时机,应该由应用程序合理控制。关于锁的使用,还有一些其他注意事项,这块也一并写一下。

  • 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
  • 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
  • 在 Lock() 之前使用 Unlock() 会导致 panic 异常
  • 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
  • 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
  • 适用于读写不确定,并且只有一个读或者写的场景

缓冲通道实现互斥逻辑

当然,我们还可以通过缓冲为1的通道实现互斥锁的逻辑。

package mainimport ("fmt""sync""time"
)type Info struct {Value string
}func Update(info *Info, sem chan bool) {fmt.Printf("%s: before update. Value: %s\n", time.Now().Format(timeFormat), info.Value)sem <- truedefer func() {<- sem}()time.Sleep(2 * time.Second)fmt.Printf("%s: in update. Value: %s\n", time.Now().Format(timeFormat), info.Value)info.Value = "update"fmt.Printf("%s: after update. Value: %s\n", time.Now().Format(timeFormat), info.Value)
}const timeFormat = "2006-01-02 15:04:05"func main() {sem := make(chan bool, 1)fmt.Printf("%s: main start\n", time.Now().Format(timeFormat))info := &Info{}var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()Update(info, sem)}()time.Sleep(time.Second)sem <- trueinfo.Value = "main"fmt.Printf("%s: in main. Value: %s\n", time.Now().Format(timeFormat), info.Value)<- semwg.Wait()
}
2024-04-17 00:26:25: main start
2024-04-17 00:26:25: before update. Value: 
2024-04-17 00:26:26: in main. Value: main
2024-04-17 00:26:27: in update. Value: main
2024-04-17 00:26:27: after update. Value: update

http://www.ppmy.cn/devtools/9583.html

相关文章

算法打卡day37

今日任务&#xff1a; 1&#xff09;1049. 最后一块石头的重量 II 2&#xff09;494. 目标和 3&#xff09;474.一和零 4&#xff09;复习day12 1049. 最后一块石头的重量 II 题目链接&#xff1a;1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 题目难…

python 绘图

这里写目录标题 绘制稍微复杂函数绘制条形图 一列有两条柱绘制普通折线图 绘制稍微复杂函数 import numpy as np import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签SimHei plt.rcParams[axes.unicode_minus] False # 用来正…

VSCode 配置 C/C++ 环境

1 安装 VSCode 直接去官网(https://code.visualstudio.com/)下载并安装即可。 2 配置C/C编译环境 方案一 如果是在Windows&#xff0c;需要安装 MingW&#xff0c;可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 w…

金融领域思考-前言

1背景介绍 不知不觉已经进入金融领域并且从事支付相关研发工作2年&#xff0c;2年了&#xff0c;应该是一个非常重要的分水岭。但越学习&#xff0c;越了解&#xff0c;越知道金融领域的复杂性。故希望借助写博客整理相关思绪&#xff0c;每有会意&#xff0c;便会记录&#x…

用于时空交通数据插补的多注意张量完成网络

用于时空交通数据插补的多注意张量完成网络 摘要:道路传感器在物联网(IoT)中的广泛部署可以实现细粒度的数据集成,这是数据驱动应用程序的基本需求。 由于网络通信不稳定、传感器故障等,不可避免地丢失和实质性异常的传感数据是不可避免的。最近的张量补全研究通过精确捕获…

推荐13个开源的Java小游戏,个个经典,好学又好玩!(附完整源代码)

Java小游戏是使用Java编程语言开发的一种娱乐应用程序。它可以在Java虚拟机上运行&#xff0c;并且具有交互性和可玩性。Java小游戏通常包含图形界面、音效、用户输入等元素&#xff0c;可以提供给用户一种娱乐和休闲的体验。 Java小游戏的开发可以利用Java的图形库&#xff0…

Linux ab详解

前言 ab是apachebench命令的缩写&#xff0c;ab是apache自带的压力测试工具。ab非常实用&#xff0c;它不仅可以对apache服务器进行网站访问压力测试&#xff0c;也可以对或其它类型的服务器进行压力测试。比如nginx、tomcat、IIS等。 ab的原理&#xff1a;ab命令会创建多个并发…

蓝桥杯刷题-乌龟棋

312. 乌龟棋 - AcWing题库 /* 状态表示&#xff1a;f[b1,b2,b3,b4]表示所有第 i种卡片使用了 bi张的走法的最大分值。状态计算&#xff1a;将 f[b1,b2,b3,b4]表示的所有走法按最后一步选择哪张卡片分成四类&#xff1a;第 i类为最后一步选择第 i种卡片。比如 i2&#xff0c;则…