Go中sync.Mutex 加锁失效的问题解决

 更新时间:2024年08月20日 10:22:51   作者:Looooking  
sync.Mutex是Go标准库中常用的一个排外锁,本文主要介绍了Go中sync.Mutex 加锁失效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

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

起因

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

package main

import (
	"fmt"
	"sync"
	"time"
)


type Info struct {
	mu sync.Mutex
	Value 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.WaitGroup
	wg.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 main

import (
	"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 <- true
	defer 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.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		Update(info, sem)
	}()
	time.Sleep(time.Second)
	sem <- true
	info.Value = "main"
	fmt.Printf("%s: in main. Value: %s\n", time.Now().Format(timeFormat), info.Value)
	<- sem
	wg.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

到此这篇关于Go中sync.Mutex 加锁失效的问题解决的文章就介绍到这了,更多相关Go sync.Mutex 加锁失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Go多线程中数据不一致问题的解决方案(sync锁机制)

    Go多线程中数据不一致问题的解决方案(sync锁机制)

    在Go语言的并发编程中,如何确保多个goroutine安全地访问共享资源是一个关键问题,Go语言提供了sync包,其中包含了多种同步原语,用于解决并发编程中的同步问题,本文将详细介绍sync包中的锁机制,需要的朋友可以参考下
    2024-10-10
  • golang利用函数闭包实现简单的中间件

    golang利用函数闭包实现简单的中间件

    中间件设计模式是一种常见的软件设计模式,它在许多编程语言和框架中被广泛应用,这篇文章主要为大家介绍一下golang利用函数闭包实现一个简单的中间件,感兴趣的可以了解下
    2023-10-10
  • Golang巧用defer进行错误处理的方法

    Golang巧用defer进行错误处理的方法

    错误处理是程序的重要组成部分,有效且优雅的处理错误是大多数程序员的追求,下面这篇文章主要给大家介绍了关于Golang中巧用defer进行错误处理的方法,文中通过示例介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-05-05
  • Go语言反射获取类型属性和方法示例

    Go语言反射获取类型属性和方法示例

    这篇文章主要为大家介绍了本文将介绍 Go 反射 reflect.StructField 和 reflect.Method 相关的内容,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go单元测试工具gomonkey的使用

    Go单元测试工具gomonkey的使用

    本文主要介绍了Go单元测试工具gomonkey的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • go使用makefile脚本编译应用的方法小结

    go使用makefile脚本编译应用的方法小结

    makefile可以看作是make工具的脚本文件, 而make主要用来处理一系列命令。常用的比如用来编译和打包文件, 在C/C++的编译打包中应用最广泛了,这篇文章主要介绍了go使用makefile脚本编译应用,需要的朋友可以参考下
    2022-08-08
  • Go Gin实现文件上传下载的示例代码

    Go Gin实现文件上传下载的示例代码

    这篇文章主要介绍了Go Gin实现文件上传下载的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Golang如何用gorm实现分页的功能

    Golang如何用gorm实现分页的功能

    这篇文章主要介绍了Golang如何用gorm实现分页的功能方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-06-06
  • Go语言简介和环境配置

    Go语言简介和环境配置

    Go语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发速度和易维护性,有人形容Go语言:Go= C + Python , 说明Go语言既有C静态语言程序的运行速度,又能达到Python动态语言的快速开发,这篇文章主要介绍了Go介绍和环境配置,需要的朋友可以参考下
    2022-07-07
  • golang中有序Map的实现

    golang中有序Map的实现

    Go语言中XContainerOrderedMap库,解决标准map无序问题,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06

最新评论