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() 方法,因此锁定顺序保持不变。

该函数也可能采用包含索引的数组,它将跳过(如果您愿意,也可以不跳过)。

这样就无法更改锁定互斥锁的顺序,因为循环的顺序是一致的。