Go Pattern: Context-aware Lock

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.

Share :

Related Posts

Value vs Pointer Receivers

Value vs Pointer Receivers

Should I use value receivers or pointer receivers? Value receivers have some benefits include immutability, concurrent safety and clean logic (not always, often true). But to what extend can I use value receivers without an issue or performance penalty?

Read More
Go Pattern: Buffered Writer

Go Pattern: Buffered Writer

A buffered writer is so ubiquitous that we do not usually consider it as a pattern, but sometimes we reinvent it or even do it in an inferior way. Let us look at a real use case first.

Read More