如何检查Go中的互斥锁是否被锁定
TL;DR: 我们能在Go中检查互斥锁是否被锁定吗?是的,但不能使用互斥锁API。这里有一个用于调试构建的解决方案。
虽然你可以对互斥锁执行Lock()
或Unlock()
操作,但无法检查它是否已被锁定。尽管这是一个合理的省略(例如,由于可能的竞态条件;另见为什么不能检查互斥锁是否被锁定?),但拥有这样的功能对于测试软件是否按预期工作仍然很有用。
换句话说,最好有一个专门用于调试构建的AssertMutexLocked
函数,可以这样使用:
1
2
3
4
5
|
// 这个方法应该总是在o.lock锁定的情况下调用
func (o *Object) someMethodImpl() {
AssertMutexLocked(&o.lock)
// (...)
}
|
拥有这样的函数将使我们能够确认给定互斥锁已被锁定的假设,并在将其添加到现有代码库时发现潜在的错误。事实上,在官方Go仓库中有一个关于添加此确切功能的GitHub问题(golang/go#1366),但它以WontFix状态关闭。
我还通过优秀的grep.app项目了解到,许多项目都有类似的互斥锁前置条件,例如google/gvisor、ghetovoice/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程序访问state字段,因为该字段未导出(其名称不以大写字母开头)。幸运的是,仍然可以使用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创建了一个包含这些函数的小型库——它仅在带有调试标签的构建中启用断言检查。
注意:尽管Go还有其他检测竞态条件的工具,例如Go的竞态检测器或Trail of Bits的OnEdge,但这些工具只会在问题情况发生时检测到,不允许你断言互斥锁前置条件是否成立。
我们一直在开发工具来帮助你更快更智能地工作。需要为你的下一个项目提供帮助吗?联系我们!
如果你喜欢这篇文章,请分享:
Twitter
LinkedIn
GitHub
Mastodon
Hacker News