在Go语言中无死锁地锁定多个锁

huangapple go评论142阅读模式
英文:

Deadlock-free locking multiple locks in Go

问题

在Golang中,有一种被证明有效的编程方式可以实现多个互斥锁(Mutexes)/锁(Locks)/其他的互斥排斥吗?例如:

mutex1.Lock()
defer mutex1.Unlock()
mutex2.Lock()
defer mutex2.Unlock()
mutex3.Lock()
defer mutex3.Unlock()

我猜这段代码会在等待mutex2/mutex3时保持mutex1被锁定。对于多个使用不同子集的锁的goroutine,这很容易导致死锁。

那么,有没有办法只在所有锁都可用时才获取这些锁?或者有没有其他模式(例如使用通道)可以实现相同的效果?

英文:

Is there a proven programmatic way to achieve mutual exclusion of multiple Mutexes / Locks / whatever in Golang?
Eg.

mutex1.Lock()
defer mutex1.Unlock()
mutex2.Lock()
defer mutex2.Unlock()
mutex3.Lock()
defer mutex3.Unlock()

would keep mutex1 locked I guess while waiting for mutex2 / mutex3. For several goroutines, all using a different subset of several locks, this easily can get deadlocked.

So is there any way to acquire those locks only if all of them are available? Or is there any other pattern (using channels maybe?) in achieving the same?

答案1

得分: 3

只有当所有锁都可用时,才能获取这些锁吗?

不是的,至少使用标准库的互斥锁(mutex)是不行的。没有办法“检查”锁是否可用,或者“尝试”获取锁。每次调用Lock()都会阻塞,直到成功获取锁。

互斥锁的实现依赖于原子操作,这些操作一次只对一个值进行操作。为了实现你所描述的情况,你需要一种“元锁”机制,其中锁的获取和释放方法本身受到锁的保护,但这可能并不是必要的,只要在程序中正确且安全地进行锁定即可。

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的顺序解锁。

在playground上的示例程序中尝试一下。为了避免死锁,请将const safe = true改为const safe = true

英文:

> So is there any way to acquire those locks only if all of them are available?

No. Not with standard library mutex at least. There is no way to "check" if a lock is available, or "try" acquiring a lock. Every call of Lock() will block until locking is successful.

The implementation of mutex relies on atomic operations which only act on a single value at once. In order to achieve what you describe, you'd need some kind of "meta locking" where execution of the lock and unlock methods are themselves protected by a lock, but this probably isn't necessary just to have correct and safe locking in your program:

Penelope Stevens' comment explains correctly: As long as the order of acquisition is consistent between different goroutines, you won't get deadlocks, even for arbitrary subsets of the locks, if each goroutine eventually releases the locks it acquires.

If the structure of your program provides some obvious locking order, then use that. If not, you can create your own special mutex type that has intrinsic ordering.

type Mutex struct {
	sync.Mutex
	id uint64
}


var mutexIDCounter uint64
func NewMutex() *Mutex {
	return &amp;Mutex{
		id: atomic.AddUint64(&amp;mutexIDCounter, 1),
	}
}

func MultiLock(locks ...*Mutex) {
	sort.Slice(locks, func(i, j int) bool { return locks[i].id &lt; locks[j].id })
	
	for i := range locks {
		locks[i].Lock()
	}
}

func MultiUnlock(locks ...*Mutex) {
	for i := range locks {
		locks[i].Unlock()
	}
}

Usage:

a := NewMutex()
b := NewMutex()
c := NewMutex()
	
MultiLock(a, b, c)
MultiUnlock(a, b, c)

Each mutex is assigned an incrementing ID, and unlocked in order of ID.

Try it for yourself in this example program on the playground. To prevent the deadlock, change const safe = true.

答案2

得分: -1

你可以将所有的互斥锁放入一个数组或映射中,并实现一个函数,使用基于范围的循环调用每个互斥锁的Lock()方法,以保持锁定的顺序不变。

该函数还可以接受一个包含应跳过的索引的数组(或不跳过,如果你愿意)。

这样,由于循环的顺序是一致的,就没有办法改变锁定互斥锁的顺序。

英文:

You might as well just put all the Mutexes into an array or into a map and implement a function that will call Lock() method on each of those using range based loop, so the order of locking remains the same.

The function might also take an array containing indexes it shall skip(or not skip if you like).

This way there will be no way to change the order of locking the mutexes as the order of the loop is consistent.

huangapple
  • 本文由 发表于 2021年6月8日 05:13:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/67878753.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定