Reputation: 11282
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?
Upvotes: 1
Views: 2395
Reputation: 8425
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 &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()
}
}
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
.
Upvotes: 3
Reputation: 1
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.
Upvotes: -1