We often use Mutex
or RWMutex
as locks in Go, but sometimes we need a lock that can be cancelled by a context during the lock attempt.
The pattern is simple - we use a channel with length 1:
lockChan := make(chan struct{}, 1)
lockChan <- struct{}{} // lock
<- lockChan // unlock
When multiple goroutines try to obtain the lock, only one of them is able to fill into the only slot, and the rest are blocked until the slot is empty again after a readout.
Unlike mutexes, we could easily cancel the lock attempt with a context:
select {
case <-ctx.Done():
// cancelled
case lockChan <- struct{}{}:
// locked
}
Let’s wrap it up:
type CtxMutex struct {
ch chan struct{}
}
func (mu *CtxMutex) Lock(ctx context.Context) bool {
select {
case <-ctx.Done():
return false
case mu.ch <- struct{}{}:
return true
}
}
func (mu *CtxMutex) Unlock() {
<- mu.ch
}
func (mu *CtxMutex) Locked() bool {
return len(mu.ch) > 0 // locked or not
}
Further, context.WithTimeout
could be used to apply a timeout to the lock.