Go中的无死锁锁定多个锁
Deadlock-free locking multiple locks in Go
在 Golang 中是否有一种经过验证的编程方式来实现多个互斥锁/锁/其他任何东西的互斥?
例如
mutex1.Lock()
defer mutex1.Unlock()
mutex2.Lock()
defer mutex2.Unlock()
mutex3.Lock()
defer mutex3.Unlock()
我猜在等待 mutex2 / mutex3 时会保持 mutex1 锁定。对于多个 goroutine,都使用多个锁的不同子集,这很容易陷入僵局。
那么有没有什么办法只有在所有锁都可用的情况下才能获取这些锁?或者是否有任何其他模式(可能使用渠道?)来实现同样的目标?
So is there any way to acquire those locks only if all of them are available?
没有。至少不使用标准库互斥锁。没有办法“检查”锁是否可用,或“尝试”获取锁。 Lock()
的每次调用都会阻塞,直到锁定成功。
互斥量的实现依赖于一次只作用于单个值的原子操作。为了实现您所描述的,您需要某种“元锁定”,其中 lock 和 unlock 方法的执行本身受到锁的保护,但这可能不是必需的,只是为了在您的中进行正确和安全的锁定程序:
Penelope Stevens 的评论解释正确:只要不同 goroutine 之间的获取顺序一致,如果每个 goroutine 最终释放它获取的锁,即使对于锁的任意子集,也不会出现死锁。
如果您的程序结构提供了一些明显的锁定顺序,则使用它。如果没有,您可以创建自己的具有内在顺序的特殊互斥锁类型。
type Mutex struct {
sync.Mutex
id uint64
}
var mutexIDCounter uint64
func NewMutex() *Mutex {
return &Mutex{
id: atomic.AddUint64(&mutexIDCounter, 1),
}
}
func MultiLock(locks ...*Mutex) {
sort.Slice(locks, func(i, j int) bool { return locks[i].id < locks[j].id })
for i := range locks {
locks[i].Lock()
}
}
func MultiUnlock(locks ...*Mutex) {
for i := range locks {
locks[i].Unlock()
}
}
用法:
a := NewMutex()
b := NewMutex()
c := NewMutex()
MultiLock(a, b, c)
MultiUnlock(a, b, c)
每个互斥量都分配了一个递增的 ID,并按 ID 的顺序解锁。
在这个 example program 操场上亲自尝试一下。为防止死锁,更改 const safe = true
.
您不妨将所有互斥体放入数组或映射中,并实现一个函数,该函数将在每个使用基于范围的循环的对象上调用 Lock() 方法,因此锁定顺序保持不变。
该函数也可能采用包含索引的数组,它将跳过(如果您愿意,也可以不跳过)。
这样就无法更改锁定互斥锁的顺序,因为循环的顺序是一致的。
在 Golang 中是否有一种经过验证的编程方式来实现多个互斥锁/锁/其他任何东西的互斥? 例如
mutex1.Lock()
defer mutex1.Unlock()
mutex2.Lock()
defer mutex2.Unlock()
mutex3.Lock()
defer mutex3.Unlock()
我猜在等待 mutex2 / mutex3 时会保持 mutex1 锁定。对于多个 goroutine,都使用多个锁的不同子集,这很容易陷入僵局。
那么有没有什么办法只有在所有锁都可用的情况下才能获取这些锁?或者是否有任何其他模式(可能使用渠道?)来实现同样的目标?
So is there any way to acquire those locks only if all of them are available?
没有。至少不使用标准库互斥锁。没有办法“检查”锁是否可用,或“尝试”获取锁。 Lock()
的每次调用都会阻塞,直到锁定成功。
互斥量的实现依赖于一次只作用于单个值的原子操作。为了实现您所描述的,您需要某种“元锁定”,其中 lock 和 unlock 方法的执行本身受到锁的保护,但这可能不是必需的,只是为了在您的中进行正确和安全的锁定程序:
Penelope Stevens 的评论解释正确:只要不同 goroutine 之间的获取顺序一致,如果每个 goroutine 最终释放它获取的锁,即使对于锁的任意子集,也不会出现死锁。
如果您的程序结构提供了一些明显的锁定顺序,则使用它。如果没有,您可以创建自己的具有内在顺序的特殊互斥锁类型。
type Mutex struct {
sync.Mutex
id uint64
}
var mutexIDCounter uint64
func NewMutex() *Mutex {
return &Mutex{
id: atomic.AddUint64(&mutexIDCounter, 1),
}
}
func MultiLock(locks ...*Mutex) {
sort.Slice(locks, func(i, j int) bool { return locks[i].id < locks[j].id })
for i := range locks {
locks[i].Lock()
}
}
func MultiUnlock(locks ...*Mutex) {
for i := range locks {
locks[i].Unlock()
}
}
用法:
a := NewMutex()
b := NewMutex()
c := NewMutex()
MultiLock(a, b, c)
MultiUnlock(a, b, c)
每个互斥量都分配了一个递增的 ID,并按 ID 的顺序解锁。
在这个 example program 操场上亲自尝试一下。为防止死锁,更改 const safe = true
.
您不妨将所有互斥体放入数组或映射中,并实现一个函数,该函数将在每个使用基于范围的循环的对象上调用 Lock() 方法,因此锁定顺序保持不变。
该函数也可能采用包含索引的数组,它将跳过(如果您愿意,也可以不跳过)。
这样就无法更改锁定互斥锁的顺序,因为循环的顺序是一致的。