Go语言中检查互斥锁状态的实用技巧

本文详细介绍了如何在Go语言中通过反射机制检查sync.Mutex和sync.RWMutex的锁定状态,提供了完整的代码实现和实际应用场景,帮助开发者在调试阶段验证互斥锁的正确使用。

如何检查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.Mutexsync.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

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计