英文:
How can this pattern result in a deadlock?
问题
我有一个(LRU)缓存对象,遇到了死锁...这怎么可能?
type cache struct {
mutex *sync.Mutex
...
}
func (this *cache) Init() { // 确保在main()函数中只调用一次
this.mutex = &sync.Mutex{}
}
func (this *cache) f1() {
// 在每个需要的'cache'函数的顶部使用的访问mutex的模式
this.mutex.Lock()
defer this.mutex.Unlock()
...
}
func (this *cache) f2() {
this.mutex.Lock()
defer this.mutex.Unlock()
...
}
在每个函数中,mutex
都是按照这种模式访问的。然而...我遇到了死锁。这怎么可能?
注意:这段代码在生产服务器上运行了10个月,这是我第一次遇到这个问题。
编辑:根据回答,f1()可以间接调用f2()导致死锁。但在我的代码中,这种情况并没有发生,所以我真的很好奇。
英文:
I have a (LRU) cache object and I encounter a deadlock... How is this possible?
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()
...
}
In every function mutex
appears, it is accessed with this pattern only.
And yet... I get a deadlock. How come this is possible?
Note: This code has been running on a production server for 10 months and it is the first time I get that.
EDIT: so f1() can call (indirectly) f2() to get a deadlock based on the answers. True but in my code this doesn't happen, so I really wonder
答案1
得分: 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")
}
输出结果(在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
。在这里阅读更多相关信息:https://stackoverflow.com/questions/23482068/in-go-is-naming-the-receiver-variable-self-misleading-or-good-practice/23494386#23494386
你可以嵌入互斥锁,使其使用更方便,并消除初始化的需要。在这里阅读更多相关信息:https://stackoverflow.com/questions/44949467/when-do-you-embed-mutex-in-struct-in-go/44950096#44950096
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上尝试一下。还要注意,这会固有地暴露互斥锁(因为嵌入类型以小写字母开头),因此任何人都可以调用Lock()
和Unlock()
方法。具体情况取决于是否是问题。
英文:
Deadlock may easily occur if one method of cache
calls another method, and both contain the Lock()
call.
See this example:
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")
}
Output (try it on the 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
Note that there does not need to have a direct call from one method to the other, it may also be a transitive call. For example cache.f1()
may call foo()
which may be a "standalone" function, and if foo()
calls cache.f2()
, we're at the same deadlock.
Improvements:
Don't name your receiver this
, it is not idiomatic. You may simply call it c
. Read more about it here: https://stackoverflow.com/questions/23482068/in-go-is-naming-the-receiver-variable-self-misleading-or-good-practice/23494386#23494386
You may embed mutexes, making it convenient to use and eliminate the need for initialization. Read more about it here: https://stackoverflow.com/questions/44949467/when-do-you-embed-mutex-in-struct-in-go/44950096#44950096
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")
}
Of course this also causes a deadlock. Try it on the Go Playground. Also note that this inherently exposes the mutex (as the embedded type starts with lowecae letter), so anyone will be able to call the Lock()
and Unlock()
methods. Depends on case whether this is a problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论