首页 > 编程语言 >Go 语言并发编程之互斥锁详解 sync.Mutex

Go 语言并发编程之互斥锁详解 sync.Mutex

时间:2024-09-29 08:52:43浏览次数:5  
标签:goroutine sync mu 互斥 Unlock Mutex time main

01 

介绍

Go 标准库 sync 提供互斥锁 Mutex。它的零值是未锁定的 Mutex,即未被任何 goroutine 所持有,它在被首次使用后,不可以复制。

我们可以使用 Mutex 限定同一时间只允许一个 goroutine 访问和修改临界区。

02 

使用

在介绍怎么使用 Mutex 之前,我们先阅读 `sync.Mutex` 源码[1]

// [the Go memory model]: https://go.dev/ref/mem
type Mutex struct {
 state int32
 sema  uint32
}
func (m *Mutex) Lock() {
// ...
}
func (m *Mutex) TryLock() bool {
// ...
}
func (m *Mutex) Unlock() {
// ...
}

 

阅读源码,我们可以发现,Mutex 提供了三个方法,分别是 LockUnlock 和 Go 1.18 新增的 TryLock

我们可以使用 MutexLock 方法获取锁,获取锁的 goroutine 可以访问和修改临界区,此时其它 goroutine 如果也想要访问和修改临界区,则会被阻塞,等待当前获取锁的 goroutine 释放锁。

持有锁的 goroutine 释放锁,可以使用  MutexUnlock 方法。

细心的读者朋友们,可能已经发现,Go 1.18 新增的 TryLock 是三个方法中唯一有返回值的方法,因为 TryLock 方法可以通过 bool 返回值通知当前准备争抢锁的 goroutine 是否抢到锁,该 goroutine 可以根据返回值决定做什么,而不仅是被阻塞,还可以自由选择做其它事情。

推荐读者朋友们阅读 the Go memory model[2],更加深入了解 Mutex

使用方式

单变量

func main() {
    // 定义变量 mu
    var mu sync.Mutex
    
    go func() {
        mu.Lock()
        fmt.Println("g1 get lock")
        time.Sleep(time.Second * 10)
        mu.Unlock()
    }()
    
    time.Sleep(time.Second * 5)
    
    // main goroutine
    if mu.TryLock() {
        fmt.Println("main get lock")
        mu.Unlock()
    } else {
        // main goroutine not get lock, do other thing
        fmt.Println("do other things first")
    }
}

  

struct 字段

type Counter struct {
    mu sync.Mutex
    count map[string]int
}

func main() {
    c := &Counter{
        count: make(map[string]int),
    }
    
    go func() {
        c.mu.Lock()
        c.count["lucy"] = 1
        fmt.Println("g1 goroutine:",c.count)
        time.Sleep(time.Second * 5)
        c.mu.Unlock()
    }()
    
    time.Sleep(time.Second * 2)
    
    // main goroutine
    c.count["lucy"] = 10
    fmt.Println("main goroutine:",c.count)
}

  

阅读代码,细心的读者朋友们可能已经发现,不管是单变量,还是作为 struct 中的字段,我们都未初始化 sync.Mutex 类型的变量,而是直接使用它的零值。

当然,初始化也可以。

03 

陷阱

想要用好 Mutex,我们还需要注意一些“陷阱”。

陷阱一

Go 语言中的互斥锁 Mutex,即使一个 goroutine 未持有锁,它也可以执行 Unlock 释放锁。

如果一个 goroutine 先使用 Unlock 释放锁,则会触发 panic。不管被释放的锁是一个未被任何 goroutine 持有的锁,还是正在被其它 goroutine 持有中的锁。

所以,我们在使用互斥锁 Mutex 时,遵循 “谁持有,谁释放” 原则。

陷阱二

假如我们在使用 Mutex 时,只使用 Lock 持有锁,而忘记使用 Unlock 释放锁,则会导致被阻塞中的 goroutine 一直被阻塞。

所以,我们在使用 Lock 时,可以在 mu.Lock() 后面,紧接着写一行 defer mu.Unlock(),当然,也要根据实际情况,灵活使用释放锁的方式,不一定必须使用 defer 的方式。

陷阱三

互斥锁 Mutex 在被首次使用后,不可以复制。

func main() {
    var mu sync.Mutex
    var mu2 sync.Mutex
    
    go func(){
        mu.Lock()
        defer mu.Unlock()
        fmt.Println("g1 goroutine")
        time.Sleep(time.Second * 10)
    }()
    
    time.Sleep(time.Second * 5)
    
    mu2 = mu
    
    mu2.Lock()
    fmt.Println("main goroutine")
    mu2.Unlock()
}

  

 

阅读代码,mu2 复制 mu 的值,程序会报错,因为 mu 已经被 goroutine 调用,它的底层值已经发生变化,所以 mu2 得到的不是一个零值的 Mutex

不过该错误可以被 go vet 检查到。

04 

延伸

我们在文中使用的代码,可以很容易知道临界区。但是,在实际项目中,我们会有一些复杂代码,即不太容易知道临界区的代码。

此时,我们可以使用数据竞争检测器,即 -race,需要注意的是,它是在运行时进行数据竞争检测,并且它比较耗费内存,在生产环境中不要使用。

使用方式:

go run -race main.go

05 

总结

本文我们介绍 Go 并发编程中,经常会使用的 sync 标准库中的互斥锁 Mutex

文中的示例代码,未给出输出结果,意在希望读者朋友们可以亲自动手执行代码,这样可以帮助大家理解文章内容。

标签:goroutine,sync,mu,互斥,Unlock,Mutex,time,main
From: https://www.cnblogs.com/dfsxh/p/18438798

相关文章

  • c# async await详解
    asyncawait传染性async/await具有传染性其实指的是你需要把异步函数的结果包装在Task类型当中。之所以c#要加async的主要原因是之前的await不是关键字,老代码可能会把await作为变量名,为了兼容性才加了async明确标识函数是continuation。await不能省略,在语义上有违直觉,为什么T......
  • semi-sync原主库加入集群阻塞问题分析
    问题现象客户在一个一主两从的半同步复制环境下做了手工切换,然后尝试把原主库加入集群中,结果发现新集群中的数据一直无法同步到slave(原主库)中来,查看slave(原主库)同步状态,IO线程和SQL线程都是YES状态,但是Seconds_Behind_Master大于0.查看showprocesslist状态,发现SQL线程一......
  • 一篇文章讲清楚synchronized关键字的作用及原理
    概述在应用Sychronized关键字时需要把握如下注意点:一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁synchronized修饰......
  • Don't Block on Async Code 不要阻止异步代码
    翻译自 Don'tBlockonAsyncCode(stephencleary.com)ThisisaproblemthatisbroughtuprepeatedlyontheforumsandStackOverflow.Ithinkit’sthemost-askedquestionbyasyncnewcomersoncethey’velearnedthebasics.这是论坛和StackOverflow上反复......
  • 服务器数据恢复—SAN环境下LUN Mapping错误导致写操作不互斥,文件系统一致性出错的数据
    服务器数据恢复环境:SAN环境下一台存储设备中有一组由6块硬盘组建的RAID6磁盘阵列,划分若干LUN,MAP到不同业务的SOLARIS操作系统服务器上。服务器故障:用户新增了一台服务器,将存储中的某个LUN映射到新增加的这台服务器上。这个映射的LUN其实之前已经MAP到其他SOLARIS操作系统的服务......
  • Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF
    一、问题描述 Redis日志:2110:M24Apr02:01:02.058*AsynchronousAOFfsyncistakingtoolong(diskisbusy?).WritingtheAOFbufferwithoutwaitingforfsynctocomplete,thismayslowdownRedis.2110:M24Apr02:01:12.862#Connectionwithslaveclientid......
  • PostSync介绍
    PostSync促进技术文章发展介绍这是一个开源的同步文章的软件,你可以使用它来同步你的文章到多个平台。使用打开浏览器,登录各个平台的账号,掘金、CSDN、知乎、公众号、哔哩哔哩、博客园、个人WordPress打开config.yaml文件,配置你的浏览器信息以及浏览器用户数据目录运行命......
  • sersync+rsync实现服务器文件实时同步
    sersync+rsync实现服务器文件实时同步一、为什么要用rsync+sersync架构?1、sersync是基于inotify开发的,类似于inotify-tools的工具2、sersync可以记录下被监听目录中发生变化的(包括增加、删除、修改)具体某一个文件或者某一个目录的名字,然后使用r......
  • rsync+inotfiy文件同步
    rsync+inotfiy文件同步1.部署rsync服务yuminstallrsync#安装rsync,如果嫌yum版本过低也可以源码安装2.vim/etc/rsyncd.conf#默认rsync没有配置文件,创建一个,文件中#和汉字仅为注释,使用中请将所有注释清除......
  • 初步学习async/await,Task.GetAwaiter,Task.Result
    初步学习async/await,Task.GetAwaiter,Task.Result   网上关于async/await的知识有很多,看了很多但不如自己实践一遍来得快,所以这里记录下我的理解和大家学习下。  首先以最简单的同步方法来开始如下privatestaticvoidTest(){Console.Wr......