我先声明一下,并不是真的加锁失效,而是我之前的理解有误,导致看起来像是加锁失效一样。于是乎记录一下,加深一下印象。
我之前有个理解误区(不知道大家有没有,有的话赶紧纠正一下——其实也是因为我这块的知识掌握不牢固导致的):觉得只要是加锁后,在我主动调用解锁之前,这个块范围内的变量一定不会被其他地方修改。后来验证发现,我大错特错了。
起因
最近在学习 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