这种模式如何导致死锁?

Tho*_*mas 0 deadlock go

我有一个(LRU)缓存对象,我遇到了死锁......这怎么可能?

  type cache struct {
    mutex *sync.Mutex
    ...
  }

  func (this *cache) Init() {  // guaranteed to be called once, in main()
    this.mutex = &sync.Mutex{}
  }

  func (this *cache) f1() {
     // Pattern for accessing mute, at the top of any function of 'cache' where needed.
     this.mutex.Lock()
     defer this.mutex.Unlock()
     ...
  }


  func (this *cache) f2() {
     this.mutex.Lock()
     defer this.mutex.Unlock()
     ...
  }
Run Code Online (Sandbox Code Playgroud)

mutex出现的每个函数中,仅使用此模式访问它.然而......我陷入僵局.怎么可能呢?

注意:此代码已在生产服务器上运行了10个月,这是我第一次获得该代码.

编辑:所以f1()可以调用(间接)f2()来根据答案获得死锁.是的,但在我的代码中,这不会发生,所以我真的很想知道

icz*_*cza 5

如果一种方法cache调用另一种方法,并且两者都包含Lock()调用,则很容易发生死锁.

看这个例子:

func (this *cache) f1() {
    this.mutex.Lock()
    defer this.mutex.Unlock()
    this.f2()
}

func (this *cache) f2() {
    this.mutex.Lock()
    defer this.mutex.Unlock()
}

func main() {
    c := &cache{}
    c.Init()
    c.f1()
    fmt.Println("Hello, playground")
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0x1040a12c, 0x8)
    /usr/local/go/src/runtime/sema.go:62 +0x40
sync.(*Mutex).Lock(0x1040a128, 0x10429f5c)
    /usr/local/go/src/sync/mutex.go:87 +0xa0
main.(*cache).f2(0x10429f94, 0x1100c0)
    /tmp/sandbox647646735/main.go:23 +0x40
main.(*cache).f1(0x10429f94, 0xdf6e0)
    /tmp/sandbox647646735/main.go:19 +0xa0
main.main()
    /tmp/sandbox647646735/main.go:30 +0x60
Run Code Online (Sandbox Code Playgroud)

请注意,不需要从一个方法直接调用另一个方法,也可以是传递调用.例如,cache.f1()可以调用foo()哪个可能是"独立"函数,如果foo()调用cache.f2(),我们处于相同的死锁状态.

改进:

不要为接收者命名this,这不是惯用语.你可以简单地称它c.在这里阅读更多关于它:在Go中命名接收器变量'self'误导或良好实践?

您可以嵌入互斥锁,方便使用,无需初始化.在这里阅读更多相关信息:什么时候在Go中嵌入mutex?

type cache struct {
    sync.Mutex
}

func (c *cache) f1() {
    c.Lock()
    defer c.Unlock()
    c.f2()
}

func (c *cache) f2() {
    c.Lock()
    defer c.Unlock()
}

func main() {
    c := &cache{}
    c.f1()
    fmt.Println("Hello, playground")
}
Run Code Online (Sandbox Code Playgroud)

当然这也会造成僵局.在Go Playground尝试一下.还要注意,这本身就暴露了互斥体(因为嵌入式类型以lowecae字母开头),所以任何人都可以调用Lock()Unlock()方法.取决于案件是否是一个问题.