go - 这种模式如何导致死锁?

标签 go deadlock

我有一个 (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()
     ...
  }

在出现的每个函数中,mutex 只能通过这种模式访问。 然而......我遇到了僵局。这怎么可能?

注意:这段代码已经在生产服务器上运行了 10 个月,这是我第一次得到它。

编辑:因此 f1() 可以(间接)调用 f2() 以根据答案获得死锁。是的,但在我的代码中这并没有发生,所以我真的很想知道

最佳答案

如果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")
}

输出(在 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

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

改进:

不要将您的接收器命名为 this,这不是惯用的。您可以简单地称它为 c。在这里阅读更多相关信息:In Go is naming the receiver variable 'self' misleading or good practice?

您可以嵌入互斥锁,方便使用并消除初始化的需要。在这里阅读更多相关信息:When do you embed mutex in struct in Go?

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")
}

当然这也会造成死锁。在 Go Playground 上试用.另请注意,这本质上会公开互斥量(因为嵌入式类型以 lowecae 字母开头),因此任何人都可以调用 Lock()Unlock() 方法。这是否是个问题取决于具体情况。

关于go - 这种模式如何导致死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45306497/

相关文章:

json - Golang JSON 解码到字段,但不编码到 JSON 响应

database - 如何找到选中的提交按钮的ID

go - 为什么 curl 为我的用户提供了错误的 json?

sql - 防止 SQL Server 中的死锁

hash - 加密/bcrypt : hashedPassword is not the hash of the given password

go - 如何将日志从单独的包写入单独的文件

c++ - 使用回调正确清理父项和子项 (C++)

mysql - golang中查询mysql时发生死锁错误,不是在查询段中而是在out of "rows.Next()"循环之后

java - 卡在 wait() 等待线程执行返回

c - 如果我在下面的代码片段中跳过 waiting[i] = false 会发生什么?