如何检查Go中的互斥锁是否被锁定
TL;DR: 能否检查Go中的互斥锁是否被锁定?可以,但无法通过互斥锁API直接实现。这里提供一个用于调试构建的解决方案。
虽然可以对互斥锁执行Lock()或Unlock()操作,但无法直接检查其是否被锁定。尽管这是合理的设计选择(例如由于可能的竞态条件,参见[为什么不能检查互斥锁是否锁定?](Why can’t I check whether a mutex is locked?)),但此功能在测试软件行为是否符合预期时仍然非常有用。
换句话说,最好能有一个专用于调试构建的AssertMutexLocked函数,可以这样使用:
1
2
3
4
5
|
// 此方法应始终在o.lock已锁定时调用
func (o *Object) someMethodImpl() {
AssertMutexLocked(&o.lock)
// (...)
}
|
通过优秀的grep.app项目,我还发现许多项目都有类似的互斥锁前置条件检查,如google/gvisor、ghettovoice/gossip、vitessio/vitess等。
现在让我们实现MutexLocked(及其他)函数。
检查互斥锁是否锁定
要检查互斥锁是否锁定,需要读取其状态。sync.Mutex结构包含两个字段:
1
2
3
4
|
type Mutex struct {
state int32
sema uint32
}
|
state字段的位对应以下标志(来源):
1
2
3
4
5
6
7
|
const (
mutexLocked = 1 << iota // 互斥锁已锁定
mutexWoken
mutexStarving
mutexWaiterShift = iota
// (...)
)
|
如果互斥锁被锁定,其state字段会设置mutexLocked(1)位。但由于该字段未导出(名称不大写),无法直接从Go程序访问。幸运的是,通过Go反射仍可访问该字段,我在下面的代码中实现了检查给定sync.Mutex或sync.RWMutex是否锁定的函数。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package main
import (
"fmt"
"reflect"
"sync"
)
const mutexLocked = 1
func MutexLocked(m *sync.Mutex) bool {
state := reflect.ValueOf(m).Elem().FieldByName("state")
return state.Int()&mutexLocked == mutexLocked
}
func RWMutexWriteLocked(rw *sync.RWMutex) bool {
// RWMutex有一个用于写锁的"w" sync.Mutex字段
state := reflect.ValueOf(rw).Elem().FieldByName("w").FieldByName("state")
return state.Int()&mutexLocked == mutexLocked
}
func RWMutexReadLocked(rw *sync.RWMutex) bool {
return reflect.ValueOf(rw).Elem().FieldByName("readerCount").Int() > 0
}
func main() {
m := sync.Mutex{}
fmt.Println("m locked =", MutexLocked(&m))
m.Lock()
fmt.Println("m locked =", MutexLocked(&m))
m.Unlock()
fmt.Println("m locked =", MutexLocked(&m))
rw := sync.RWMutex{}
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.Lock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.Unlock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.RLock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.RLock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.RUnlock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
rw.RUnlock()
fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw))
}
|
程序输出如下:
1
2
3
4
5
6
7
8
9
10
|
m locked = false
m locked = true
m locked = false
rw write locked = false read locked = false
rw write locked = true read locked = false
rw write locked = false read locked = false
rw write locked = false read locked = true
rw write locked = false read locked = true
rw write locked = false read locked = true
rw write locked = false read locked = false
|
这些函数可用于创建AssertMutexLocked和其他功能。为此,我在trailofbits/go-mutexasserts创建了一个小型库,仅在带有debug标签的构建中启用断言检查。
注意: 尽管Go还有其他竞态条件检测工具,如Go的race detector或Trail of Bits的OnEdge,但这些工具仅在问题情况发生时才能检测到,无法断言互斥锁前置条件是否成立。
我们始终致力于开发帮助您更高效、更智能工作的工具。需要下一个项目的帮助吗?请联系我们!
如果您喜欢这篇文章,请分享至:
Twitter
LinkedIn
GitHub
Mastodon
Hacker News