03|Mutex:4种易错场景大盘点

03|Mutex:4种易错场景大盘点

1.Lock/Unlock 不是成对出现

Lock/Unlock 没有成对出现,就意味着会出现死锁的情况,或者是因为 Unlock 一个未加锁的Mutex 而导致 panic。

缺少Unlock的场景,常见的三种情况:

  • if-else分支太多,漏写Unlock
  • 重构时删除Unlock
  • Unlock误写成Lock

在这种情况下,锁被获取之后,就不会被释放了,这也就意味着,其它的 goroutine 永远都没机会获取到锁。

误删Lock()

1
2
3
4
5
func foo() {
var mu sync.Mutex
defer mu.Unlock()
fmt.Println("hello world")
}

运行之后panic

image-20230304000406863

2.Copy已经使用的Mutex

Package sync 的同步原语在使用后是不能复制的。

为什么Mutex不能够复制?

Mutex是一个有状态的对象,它的state字段记录了这个锁的状态。如果复制一个已经加锁的Mutex给一个新的变量,新的初始化的变量就被加锁了,显然不符合预期,我们期待一个零值的Mutex。

在并发环境之中,我们不知道复制的Mutex状态是什么,因为要复制的 Mutex 是由其它 goroutine 并发访问的,状态可能总是在变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"sync"
)

type Counter struct {
sync.Mutex
Count int
}

func main() {
var c Counter
c.Lock()
defer c.Unlock()
c.Count++
foo2(c)
}

func foo2(c Counter) {
c.Lock()
defer c.Unlock()
fmt.Println("in foo")
}

image-20230304001403468

这里第18行调用foo函数时,复制了c作为函数的参数,但是c已经被使用,复制出来的Counter带有状态。

死锁的检查机制

这个例子中因为复制了一个使用了的 Mutex,导致锁无法使用,程序处于死锁的状态。程序运行的时候,死锁检查机制能够发现这种死锁情况并输出错误信息,如下图中错误信息以及错误堆栈。


03|Mutex:4种易错场景大盘点
http://example.com/2023/03/03/03|Mutex:4种易错场景大盘点/
Author
WYX
Posted on
March 3, 2023
Licensed under