Go 这种模式如何导致死锁?
我有一个(LRU)缓存对象,遇到死锁。。。这怎么可能Go 这种模式如何导致死锁?,go,deadlock,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 acce
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
中,只能使用此模式访问它。
然而。。。我陷入了僵局。这怎么可能呢
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()
...
}
注意:这段代码已经在生产服务器上运行了10个月,这是我第一次看到它
编辑:因此f1()可以(间接)调用f2(),以根据答案获得死锁。是的,但在我的代码中,这并没有发生,所以我真的想知道,如果
缓存的一个方法调用另一个方法,并且都包含锁()
调用,死锁可能很容易发生
请参见此示例:
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")
}
输出(在上尝试):
注意,从一个方法到另一个方法不需要直接调用,它也可以是传递调用。例如,cache.f1()
改进:
不要将你的接收者命名为this,它不是惯用的。你可以简单地称它为c
。请在此处阅读更多信息:
您可以嵌入互斥体,这样使用起来很方便,无需进行初始化。请在此处阅读更多信息:
当然,这也会导致死锁。试穿一下。还请注意,这本身就暴露了互斥锁(因为嵌入类型以Lowcae字母开头),因此任何人都可以调用Lock()
和Unlock()
方法。取决于具体情况,这是否是问题。如果缓存的一个方法调用另一个方法,并且两个方法都包含锁()
调用,则很容易发生死锁
请参见此示例:
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")
}
输出(在上尝试):
注意,从一个方法到另一个方法不需要直接调用,它也可以是传递调用。例如,cache.f1()
改进:
不要将你的接收者命名为this,它不是惯用的。你可以简单地称它为c
。请在此处阅读更多信息:
您可以嵌入互斥体,这样使用起来很方便,无需进行初始化。请在此处阅读更多信息:
当然,这也会导致死锁。试穿一下。还请注意,这本身就暴露了互斥锁(因为嵌入类型以Lowcae字母开头),因此任何人都可以调用Lock()
和Unlock()
方法。视情况而定,这是否是问题。f1是否保证退出?它是否调用锁定互斥锁的其他函数?如果缓存的一个方法调用另一个方法,并且两个方法都包含lock()
调用,则很容易发生死锁。如上所述,这取决于f1()
的作用。此外,您不需要创建新的互斥体并使用Init()
。只需在结构中使用sync.Mutex
而不是*sync,Mutex
,因为设计的零值是有效的解锁Mutex。您是否在竞赛检测器下运行了此操作?我不确定它是否能检测到可能的多个锁尝试,但它可能会揭示一些东西…@Thomas这些方法并不要求直接相互调用。它可能是cache.f1()
调用foo()
,这是一个“独立”函数,如果foo()
调用cache.f2()
,我们将处于相同的死锁状态。见编辑后的答案。如果您的代码中甚至不存在此类可传递调用,那么您需要发布一个,否则它将成为离题。f1保证退出吗?它是否调用锁定互斥锁的其他函数?如果缓存的一个方法调用另一个方法,并且两个方法都包含lock()
调用,则很容易发生死锁。如上所述,这取决于f1()
的作用。此外,您不需要创建新的互斥体并使用Init()
。只需在结构中使用sync.Mutex
而不是*sync,Mutex
,因为设计的零值是有效的解锁Mutex。您是否在竞赛检测器下运行了此操作?我不确定它是否能检测到可能的多个锁尝试,但它可能会揭示一些东西…@Thomas这些方法并不要求直接相互调用。它可能是cache.f1()
调用foo()
,这是一个“独立”函数,如果foo()
调用cache.f2()
,我们将处于相同的死锁状态。见编辑后的答案。如果您的代码中甚至不存在此类可传递调用,那么您需要发布一个,否则它将成为非主题。它仅在包级别公开互斥,因为它没有导出。对的只是澄清一下OP,否则可能会让人困惑。@reticentroot不,那不是真的。在包内部,所有包级别标识符都是可见的。除此之外,仅导出标识符。类型cache
本身不会导出,但例如,如果导出的函数返回类型cache
或*cache
的值,则任何人都可以调用其Lock()
方法。啊,是的,您的右侧嵌入了互斥体,您可以通过使用类型为mutex的未报告成员来规避该问题。当然,初始化将不得不再次处理。。。所以我想这就是lol中优秀文档的来源。它是否可以抽象为“隐藏”锁?@reticentroot在某些情况下,它可以是,例如,您使用非指针字段,但请确保使用指向包装结构的指针,以避免意外复制互斥对象,并确保该字段可寻址,因为调用具有指针接收器的Lock()
和Unlock()
方法需要该地址。这在没有初始化的情况下是可能的,因为sync.Mutex
的零值是一个有效的、未锁定的互斥体。它只在包级别公开互斥体,因为它是