lock

package
v1.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 1, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

README

Lock

Mutex implementations for thread-safe synchronization in Go.

Overview

The lock package provides enhanced mutex functionality including basic mutex operations and key-based mutex management. It offers convenient wrappers around Go's sync.Mutex with additional features like TryLock and per-key locking mechanisms.

Features

  • Basic Mutex - Wrapper around sync.Mutex with TryLock support
  • Key-Based Mutexes - Manage multiple independent locks by key
  • Non-Blocking Locks - TryLock for conditional locking
  • Thread-Safe - Built on sync.Map for concurrent access
  • On-Demand Creation - Mutexes created automatically when needed
  • Memory Management - UnlockAndDelete for cleanup

Installation

go get -u github.com/common-library/go/lock

Quick Start

Basic Mutex
import "github.com/common-library/go/lock"

var mu lock.Mutex

func criticalSection() {
    mu.Lock()
    defer mu.Unlock()
    
    // Only one goroutine executes this at a time
    sharedResource++
}
Key-Based Mutex
import "github.com/common-library/go/lock"

var mutexes lock.MutexByKey

func updateUser(userID string) {
    mutexes.Lock(userID)
    defer mutexes.Unlock(userID)
    
    // Different users can be updated concurrently
    // Same user operations are serialized
    database.Update(userID, newData)
}

API Reference

Mutex Type
type Mutex struct {
    // Unexported fields
}

Wrapper around sync.Mutex providing lock operations.

Mutex.Lock
func (m *Mutex) Lock()

Acquires the mutex, blocking until available.

Behavior:

  • Blocks until lock is acquired
  • Must be paired with Unlock
  • Calling on already-locked mutex causes deadlock
Mutex.TryLock
func (m *Mutex) TryLock() bool

Attempts to acquire the mutex without blocking.

Returns:

  • bool - true if lock acquired, false otherwise

Behavior:

  • Returns immediately (non-blocking)
  • Returns false if mutex is locked
  • Must call Unlock if returns true
Mutex.Unlock
func (m *Mutex) Unlock()

Releases the mutex.

Behavior:

  • Releases the lock
  • Panics if called on unlocked mutex
  • Should be called by same goroutine that locked
MutexByKey Type
type MutexByKey struct {
    // Unexported fields
}

Manages multiple mutexes indexed by keys.

MutexByKey.Lock
func (mbk *MutexByKey) Lock(key any)

Acquires the mutex for the given key.

Parameters:

  • key - Any comparable value to identify the mutex

Behavior:

  • Creates mutex on-demand if doesn't exist
  • Blocks until lock acquired
  • Different keys can be locked concurrently
MutexByKey.TryLock
func (mbk *MutexByKey) TryLock(key any) bool

Attempts to acquire the mutex for the key without blocking.

Parameters:

  • key - Any comparable value to identify the mutex

Returns:

  • bool - true if lock acquired, false otherwise
MutexByKey.Unlock
func (mbk *MutexByKey) Unlock(key any)

Releases the mutex for the given key.

Parameters:

  • key - The key identifying the mutex

Behavior:

  • Unlocks the mutex
  • Mutex remains in map after unlock
  • Use UnlockAndDelete to remove mutex
MutexByKey.UnlockAndDelete
func (mbk *MutexByKey) UnlockAndDelete(key any)

Releases and removes the mutex for the key.

Parameters:

  • key - The key identifying the mutex

Behavior:

  • Unlocks the mutex
  • Removes mutex from internal map
  • Frees memory
MutexByKey.Delete
func (mbk *MutexByKey) Delete(key any)

Removes the mutex without unlocking.

Parameters:

  • key - The key identifying the mutex

Warning: Only use when certain mutex is not locked.

Complete Examples

Protecting Shared Resources
package main

import (
    "fmt"
    "sync"
    
    "github.com/common-library/go/lock"
)

var (
    counter int
    mu      lock.Mutex
)

func increment() {
    mu.Lock()
    defer mu.Unlock()
    
    counter++
}

func main() {
    var wg sync.WaitGroup
    
    // Start 1000 goroutines
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    
    wg.Wait()
    fmt.Printf("Final counter: %d\n", counter) // Output: 1000
}
Non-Blocking Lock with TryLock
package main

import (
    "fmt"
    "time"
    
    "github.com/common-library/go/lock"
)

var mu lock.Mutex

func processWithFallback() {
    if mu.TryLock() {
        defer mu.Unlock()
        
        fmt.Println("Processing primary task...")
        time.Sleep(1 * time.Second)
    } else {
        fmt.Println("Primary task busy, doing alternative task...")
        doAlternativeTask()
    }
}

func doAlternativeTask() {
    fmt.Println("Alternative task completed")
}

func main() {
    // First call acquires lock
    go processWithFallback()
    
    // Second call immediately falls back
    time.Sleep(10 * time.Millisecond)
    processWithFallback()
    
    time.Sleep(2 * time.Second)
}
Retry Pattern with TryLock
package main

import (
    "fmt"
    "log"
    "time"
    
    "github.com/common-library/go/lock"
)

var mu lock.Mutex

func processWithRetry(maxRetries int) error {
    for i := 0; i < maxRetries; i++ {
        if mu.TryLock() {
            defer mu.Unlock()
            
            fmt.Println("Processing...")
            return nil
        }
        
        fmt.Printf("Retry %d/%d...\n", i+1, maxRetries)
        time.Sleep(100 * time.Millisecond)
    }
    
    return fmt.Errorf("failed to acquire lock after %d retries", maxRetries)
}

func main() {
    if err := processWithRetry(3); err != nil {
        log.Fatal(err)
    }
}
Per-User Locking
package main

import (
    "fmt"
    "sync"
    
    "github.com/common-library/go/lock"
)

var mutexes lock.MutexByKey

type User struct {
    ID      string
    Balance int
}

var users = map[string]*User{
    "user1": {ID: "user1", Balance: 100},
    "user2": {ID: "user2", Balance: 200},
}

func updateBalance(userID string, amount int) {
    mutexes.Lock(userID)
    defer mutexes.Unlock(userID)
    
    user := users[userID]
    user.Balance += amount
    
    fmt.Printf("User %s balance: %d\n", userID, user.Balance)
}

func main() {
    var wg sync.WaitGroup
    
    // Update different users concurrently (no blocking)
    wg.Add(2)
    go func() {
        defer wg.Done()
        updateBalance("user1", 50)
    }()
    go func() {
        defer wg.Done()
        updateBalance("user2", 75)
    }()
    
    // Update same user (serialized)
    wg.Add(2)
    go func() {
        defer wg.Done()
        updateBalance("user1", 10)
    }()
    go func() {
        defer wg.Done()
        updateBalance("user1", 20)
    }()
    
    wg.Wait()
}
Resource Pool Management
package main

import (
    "fmt"
    "sync"
    
    "github.com/common-library/go/lock"
)

var mutexes lock.MutexByKey

type ResourcePool struct {
    resources map[string]*Resource
}

type Resource struct {
    ID   string
    Data string
}

func (p *ResourcePool) UpdateResource(id string, data string) {
    mutexes.Lock(id)
    defer mutexes.Unlock(id)
    
    if resource, exists := p.resources[id]; exists {
        resource.Data = data
        fmt.Printf("Updated resource %s\n", id)
    }
}

func (p *ResourcePool) TryUpdateResource(id string, data string) bool {
    if mutexes.TryLock(id) {
        defer mutexes.Unlock(id)
        
        if resource, exists := p.resources[id]; exists {
            resource.Data = data
            fmt.Printf("Updated resource %s\n", id)
            return true
        }
    }
    
    fmt.Printf("Resource %s is busy\n", id)
    return false
}

func main() {
    pool := &ResourcePool{
        resources: map[string]*Resource{
            "res1": {ID: "res1", Data: "initial"},
            "res2": {ID: "res2", Data: "initial"},
        },
    }
    
    var wg sync.WaitGroup
    
    wg.Add(3)
    go func() {
        defer wg.Done()
        pool.UpdateResource("res1", "data1")
    }()
    go func() {
        defer wg.Done()
        pool.UpdateResource("res2", "data2")
    }()
    go func() {
        defer wg.Done()
        pool.TryUpdateResource("res1", "data3")
    }()
    
    wg.Wait()
}
Temporary Locks with Cleanup
package main

import (
    "fmt"
    "sync"
    
    "github.com/common-library/go/lock"
)

var mutexes lock.MutexByKey

func processRequest(requestID string) {
    // Lock for this specific request
    mutexes.Lock(requestID)
    defer mutexes.UnlockAndDelete(requestID) // Clean up when done
    
    fmt.Printf("Processing request %s\n", requestID)
    // Process...
}

func main() {
    var wg sync.WaitGroup
    
    // Each request gets its own lock, cleaned up after completion
    for i := 0; i < 10; i++ {
        wg.Add(1)
        requestID := fmt.Sprintf("req-%d", i)
        
        go func(id string) {
            defer wg.Done()
            processRequest(id)
        }(requestID)
    }
    
    wg.Wait()
}
Cache Access Control
package main

import (
    "fmt"
    "time"
    
    "github.com/common-library/go/lock"
)

var mutexes lock.MutexByKey

type Cache struct {
    data map[string]string
}

func NewCache() *Cache {
    return &Cache{
        data: make(map[string]string),
    }
}

func (c *Cache) Get(key string) (string, bool) {
    mutexes.Lock(key)
    defer mutexes.Unlock(key)
    
    value, exists := c.data[key]
    return value, exists
}

func (c *Cache) Set(key string, value string) {
    mutexes.Lock(key)
    defer mutexes.Unlock(key)
    
    c.data[key] = value
}

func (c *Cache) Delete(key string) {
    mutexes.Lock(key)
    defer mutexes.UnlockAndDelete(key)
    
    delete(c.data, key)
}

func main() {
    cache := NewCache()
    
    // Concurrent access to different keys
    go cache.Set("user:1", "Alice")
    go cache.Set("user:2", "Bob")
    
    time.Sleep(100 * time.Millisecond)
    
    if value, exists := cache.Get("user:1"); exists {
        fmt.Printf("Found: %s\n", value)
    }
    
    cache.Delete("user:1")
}
Timeout Pattern
package main

import (
    "fmt"
    "time"
    
    "github.com/common-library/go/lock"
)

var mu lock.Mutex

func processWithTimeout(timeout time.Duration) error {
    deadline := time.After(timeout)
    ticker := time.NewTicker(10 * time.Millisecond)
    defer ticker.Stop()
    
    for {
        select {
        case <-deadline:
            return fmt.Errorf("timeout acquiring lock")
            
        case <-ticker.C:
            if mu.TryLock() {
                defer mu.Unlock()
                
                fmt.Println("Lock acquired, processing...")
                time.Sleep(100 * time.Millisecond)
                return nil
            }
        }
    }
}

func main() {
    // Simulate long-running lock holder
    go func() {
        mu.Lock()
        time.Sleep(500 * time.Millisecond)
        mu.Unlock()
    }()
    
    time.Sleep(50 * time.Millisecond)
    
    if err := processWithTimeout(1 * time.Second); err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Success!")
    }
}

Best Practices

1. Always Use defer with Unlock
// Good: Ensures unlock even if panic occurs
mu.Lock()
defer mu.Unlock()
// Critical section

// Avoid: Manual unlock can be missed
mu.Lock()
// Critical section
mu.Unlock() // Might not be called if panic occurs
2. Keep Critical Sections Small
// Good: Minimal lock time
mu.Lock()
data := sharedResource
mu.Unlock()

// Process data without holding lock
result := processData(data)

// Avoid: Holding lock during expensive operations
mu.Lock()
data := sharedResource
result := processData(data) // Expensive operation
mu.Unlock()
3. Use TryLock for Optional Processing
// Good: Fall back if lock unavailable
if mu.TryLock() {
    defer mu.Unlock()
    doExpensiveWork()
} else {
    doQuickAlternative()
}

// Avoid: Blocking on optional work
mu.Lock() // Might block unnecessarily
defer mu.Unlock()
doExpensiveWork()
4. Use UnlockAndDelete for Temporary Locks
// Good: Clean up one-time locks
mutexes.Lock(requestID)
defer mutexes.UnlockAndDelete(requestID)

// Avoid: Accumulating unused mutexes
mutexes.Lock(requestID)
defer mutexes.Unlock(requestID) // Mutex stays in memory
5. Use Consistent Lock Ordering
// Good: Always lock in same order
mutexes.Lock("resource-A")
defer mutexes.Unlock("resource-A")
mutexes.Lock("resource-B")
defer mutexes.Unlock("resource-B")

// Avoid: Inconsistent ordering (can cause deadlock)
// Thread 1: Lock A then B
// Thread 2: Lock B then A

Common Use Cases

1. Protecting Counters
var (
    counter int
    mu      lock.Mutex
)

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}
2. Cache Management
var (
    cache   map[string]string
    mutexes lock.MutexByKey
)

func getCached(key string) string {
    mutexes.Lock(key)
    defer mutexes.Unlock(key)
    return cache[key]
}
3. Resource Pooling
var mutexes lock.MutexByKey

func borrowResource(id string) {
    mutexes.Lock(id)
    // Use resource
}

func returnResource(id string) {
    mutexes.Unlock(id)
}
4. Rate Limiting
var mu lock.Mutex

func rateLimitedAction() bool {
    if mu.TryLock() {
        defer mu.Unlock()
        doAction()
        return true
    }
    return false // Too busy
}

Performance Tips

  1. Minimize Lock Duration - Hold locks for shortest time possible
  2. Use TryLock - Avoid blocking when work is optional
  3. Clean Up - Use UnlockAndDelete for temporary locks
  4. Fine-Grained Locking - Use MutexByKey instead of global mutex
  5. Lock Ordering - Prevent deadlocks with consistent ordering

Testing

Testing Concurrent Access
func TestConcurrentIncrement(t *testing.T) {
    var (
        counter int
        mu      lock.Mutex
        wg      sync.WaitGroup
    )
    
    iterations := 1000
    goroutines := 10
    
    for i := 0; i < goroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < iterations; j++ {
                mu.Lock()
                counter++
                mu.Unlock()
            }
        }()
    }
    
    wg.Wait()
    
    expected := goroutines * iterations
    if counter != expected {
        t.Errorf("Expected %d, got %d", expected, counter)
    }
}
Testing TryLock
func TestTryLock(t *testing.T) {
    var mu lock.Mutex
    
    // First lock succeeds
    if !mu.TryLock() {
        t.Fatal("Expected first TryLock to succeed")
    }
    
    // Second lock fails (already locked)
    if mu.TryLock() {
        t.Fatal("Expected second TryLock to fail")
    }
    
    mu.Unlock()
    
    // Third lock succeeds (after unlock)
    if !mu.TryLock() {
        t.Fatal("Expected third TryLock to succeed")
    }
    mu.Unlock()
}

Dependencies

  • sync - Go standard library

Further Reading

Documentation

Overview

Package lock provides mutex implementations for synchronization.

This package offers enhanced mutex functionality including basic mutex operations and key-based mutex management for concurrent access control.

Features:

  • Basic mutex wrapper with Lock/TryLock/Unlock
  • Key-based mutex map for managing multiple locks
  • Thread-safe mutex storage using sync.Map
  • TryLock support for non-blocking lock acquisition

Example:

var mu lock.Mutex
mu.Lock()
defer mu.Unlock()
// Critical section

var mutexes lock.MutexByKey
mutexes.Lock("resource-1")
defer mutexes.Unlock("resource-1")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Mutex

type Mutex struct {
	// contains filtered or unexported fields
}

Mutex is a wrapper around sync.Mutex providing mutex operations.

This type provides the same functionality as sync.Mutex with additional methods like TryLock for non-blocking lock attempts.

Example:

var mu lock.Mutex

// Blocking lock
mu.Lock()
defer mu.Unlock()
// Critical section

// Non-blocking lock
if mu.TryLock() {
    defer mu.Unlock()
    // Critical section
} else {
    // Lock not acquired
}

func (*Mutex) Lock

func (m *Mutex) Lock()

Lock acquires the mutex, blocking until the lock is available.

If the mutex is already locked by another goroutine, Lock blocks until the mutex becomes available. It is a run-time error if m is already locked by the calling goroutine.

Behavior:

  • Blocks the calling goroutine until lock is acquired
  • Must be paired with Unlock
  • Calling Lock on an already-locked mutex by the same goroutine causes deadlock

Example:

var mu lock.Mutex

// Acquire lock
mu.Lock()
defer mu.Unlock() // Ensure unlock

// Critical section - only one goroutine executes this at a time
fmt.Println("Processing...")
sharedResource++

Example with goroutines:

var mu lock.Mutex
var counter int

for i := 0; i < 10; i++ {
    go func() {
        mu.Lock()
        counter++
        mu.Unlock()
    }()
}

func (*Mutex) TryLock

func (m *Mutex) TryLock() bool

TryLock attempts to acquire the mutex without blocking.

TryLock tries to lock the mutex and reports whether it succeeded. Unlike Lock, TryLock does not block if the mutex is already locked.

Returns:

  • bool: true if the lock was acquired, false otherwise

Behavior:

  • Returns immediately (non-blocking)
  • Returns true if lock acquired successfully
  • Returns false if mutex is already locked
  • If true is returned, Unlock must be called to release the lock

Example:

var mu lock.Mutex

if mu.TryLock() {
    defer mu.Unlock()
    fmt.Println("Lock acquired, processing...")
    // Critical section
} else {
    fmt.Println("Lock not available, skipping...")
    // Alternative action
}

Example with retry:

var mu lock.Mutex

for i := 0; i < 3; i++ {
    if mu.TryLock() {
        defer mu.Unlock()
        // Process
        break
    }
    time.Sleep(100 * time.Millisecond)
}

Example with timeout pattern:

var mu lock.Mutex

timeout := time.After(5 * time.Second)
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()

for {
    select {
    case <-timeout:
        fmt.Println("Timeout")
        return
    case <-ticker.C:
        if mu.TryLock() {
            defer mu.Unlock()
            // Process
            return
        }
    }
}

func (*Mutex) Unlock

func (m *Mutex) Unlock()

Unlock releases the mutex.

Unlock unlocks the mutex. It is a run-time error if the mutex is not locked on entry to Unlock.

Behavior:

  • Releases the mutex for other goroutines to acquire
  • Must be called by the same goroutine that called Lock
  • Calling Unlock on an unlocked mutex causes panic
  • Should be paired with Lock or successful TryLock

A locked Mutex is not associated with a particular goroutine. It is allowed for one goroutine to lock a Mutex and then arrange for another goroutine to unlock it (though this is generally not recommended).

Example with defer:

var mu lock.Mutex

mu.Lock()
defer mu.Unlock() // Automatically unlocks when function returns

// Critical section
if err := doSomething(); err != nil {
    return err // Unlock happens automatically
}

Example without defer:

var mu lock.Mutex

mu.Lock()
// Critical section
processData()
mu.Unlock()

Example with early unlock:

var mu lock.Mutex

mu.Lock()
data := readSharedData()
mu.Unlock() // Unlock early to reduce contention

// Process data without holding the lock
result := processData(data)

type MutexByKey

type MutexByKey struct {
	// contains filtered or unexported fields
}

MutexByKey manages multiple mutexes indexed by keys.

This type maintains a thread-safe map of mutexes, allowing different resources to be locked independently using unique keys. It's useful when you need to synchronize access to multiple resources without locking all of them at once.

The underlying storage uses sync.Map for thread-safe concurrent access. Mutexes are created on-demand when first accessed.

Example:

var mutexes lock.MutexByKey

// Lock different resources independently
mutexes.Lock("user:123")
defer mutexes.Unlock("user:123")
// Update user 123

mutexes.Lock("account:456")
defer mutexes.Unlock("account:456")
// Update account 456

func (*MutexByKey) Delete

func (mbk *MutexByKey) Delete(key any)

Delete removes the mutex for the given key from the internal map.

This method removes the mutex without unlocking it first. Use with caution, as deleting a locked mutex can lead to resource leaks if other goroutines are waiting for it.

Parameters:

  • key: The key identifying the mutex to delete

Behavior:

  • Removes the mutex from the internal map
  • Does NOT unlock the mutex before deletion
  • Safe to call even if the key doesn't exist
  • No effect if the key is not in the map

Warning: Only use Delete when you're certain the mutex is not locked or when cleaning up abandoned locks. Prefer UnlockAndDelete for normal cleanup after releasing a lock.

Example for cleanup:

var mutexes lock.MutexByKey

// After determining a lock is no longer needed
// (and is definitely not locked)
mutexes.Delete("old-key")

Example for resetting state:

var mutexes lock.MutexByKey

func reset() {
    // Clear all mutexes
    for _, key := range getAllKeys() {
        mutexes.Delete(key)
    }
}

Example for conditional cleanup:

var mutexes lock.MutexByKey

if !isActive(key) {
    // Remove inactive resource's mutex
    mutexes.Delete(key)
}

Preferred pattern (use UnlockAndDelete instead):

// Good: Unlock and delete together
mutexes.UnlockAndDelete(key)

// Risky: Delete without unlock
// Only do this if you're absolutely sure it's not locked
mutexes.Delete(key)

func (*MutexByKey) Lock

func (mbk *MutexByKey) Lock(key any)

Lock acquires the mutex associated with the given key.

If no mutex exists for the key, a new one is created automatically. This operation is thread-safe and multiple goroutines can call Lock with different keys simultaneously without blocking each other.

Parameters:

  • key: Any comparable value to identify the mutex (typically string or int)

Behavior:

  • Creates mutex on-demand if it doesn't exist
  • Blocks until the mutex for the specific key is acquired
  • Must be paired with Unlock or UnlockAndDelete using the same key
  • Thread-safe: multiple keys can be locked concurrently

Example:

var mutexes lock.MutexByKey

// Lock specific user
mutexes.Lock("user:123")
defer mutexes.Unlock("user:123")

// Update user data
updateUser(123)

Example with multiple resources:

var mutexes lock.MutexByKey

// Different resources can be locked independently
go func() {
    mutexes.Lock("resource-A")
    defer mutexes.Unlock("resource-A")
    // Process A
}()

go func() {
    mutexes.Lock("resource-B")
    defer mutexes.Unlock("resource-B")
    // Process B (runs concurrently with A)
}()

Example with struct key:

type ResourceKey struct {
    Type string
    ID   int
}

key := ResourceKey{Type: "user", ID: 123}
mutexes.Lock(key)
defer mutexes.Unlock(key)

func (*MutexByKey) TryLock

func (mbk *MutexByKey) TryLock(key any) bool

TryLock attempts to acquire the mutex for the given key without blocking.

TryLock tries to lock the mutex associated with the key and reports whether it succeeded. If no mutex exists for the key, a new one is created automatically.

Parameters:

  • key: Any comparable value to identify the mutex

Returns:

  • bool: true if the lock was acquired, false otherwise

Behavior:

  • Creates mutex on-demand if it doesn't exist
  • Returns immediately without blocking
  • Returns true if lock acquired successfully
  • Returns false if mutex for the key is already locked
  • If true is returned, Unlock or UnlockAndDelete must be called

Example:

var mutexes lock.MutexByKey

if mutexes.TryLock("user:123") {
    defer mutexes.Unlock("user:123")
    fmt.Println("Processing user 123")
    updateUser(123)
} else {
    fmt.Println("User 123 is being processed, skip")
}

Example with fallback:

var mutexes lock.MutexByKey

if mutexes.TryLock("resource-A") {
    defer mutexes.Unlock("resource-A")
    processResourceA()
} else {
    // Process alternative resource if A is busy
    mutexes.Lock("resource-B")
    defer mutexes.Unlock("resource-B")
    processResourceB()
}

Example with retry:

var mutexes lock.MutexByKey
key := "critical-resource"

for i := 0; i < 3; i++ {
    if mutexes.TryLock(key) {
        defer mutexes.Unlock(key)
        processCriticalResource()
        break
    }
    time.Sleep(100 * time.Millisecond)
}

func (*MutexByKey) Unlock

func (mbk *MutexByKey) Unlock(key any)

Unlock releases the mutex associated with the given key.

Unlock unlocks the mutex for the specified key. The mutex must be locked before calling Unlock. If no mutex exists for the key, a new unlocked one is created, which may cause a panic.

Parameters:

  • key: The key identifying the mutex to unlock

Behavior:

  • Releases the mutex for other goroutines to acquire
  • Must be called with the same key used for Lock/TryLock
  • Calling Unlock on an unlocked mutex causes panic
  • The mutex remains in the map after unlocking

Note: Use UnlockAndDelete if you want to remove the mutex after unlocking to free memory for mutexes that won't be used again.

Example with defer:

var mutexes lock.MutexByKey

mutexes.Lock("user:123")
defer mutexes.Unlock("user:123")

// Update user
updateUser(123)

Example without defer:

var mutexes lock.MutexByKey

mutexes.Lock("account:456")
processAccount(456)
mutexes.Unlock("account:456")

Example with early unlock:

var mutexes lock.MutexByKey

mutexes.Lock("data:789")
data := readData(789)
mutexes.Unlock("data:789") // Unlock early

// Process without holding the lock
result := processData(data)

func (*MutexByKey) UnlockAndDelete

func (mbk *MutexByKey) UnlockAndDelete(key any)

UnlockAndDelete releases and removes the mutex for the given key.

This method unlocks the mutex and then deletes it from the internal map, freeing memory. This is useful for one-time locks or when you know a particular key won't be used again.

Parameters:

  • key: The key identifying the mutex to unlock and delete

Behavior:

  • Unlocks the mutex if it exists
  • Removes the mutex from the internal map
  • Safe to call even if the mutex doesn't exist
  • Calling on an unlocked mutex causes panic (before deletion)

Use this instead of Unlock when:

  • Processing one-time resources
  • Managing many temporary locks
  • Memory efficiency is important

Example for temporary locks:

var mutexes lock.MutexByKey

requestID := generateRequestID()
mutexes.Lock(requestID)
defer mutexes.UnlockAndDelete(requestID) // Clean up after request

processRequest(requestID)

Example for batch processing:

var mutexes lock.MutexByKey

for _, item := range items {
    key := fmt.Sprintf("item:%d", item.ID)
    mutexes.Lock(key)

    processItem(item)

    // Clean up since we won't need this lock again
    mutexes.UnlockAndDelete(key)
}

Example for session management:

var mutexes lock.MutexByKey

func handleSession(sessionID string) {
    mutexes.Lock(sessionID)
    defer mutexes.UnlockAndDelete(sessionID)

    // Process session
    // Lock is cleaned up when session ends
}

Comparison with Unlock:

// Using Unlock - mutex stays in memory
mutexes.Lock("temp-key")
mutexes.Unlock("temp-key") // Mutex still in map

// Using UnlockAndDelete - mutex is removed
mutexes.Lock("temp-key")
mutexes.UnlockAndDelete("temp-key") // Mutex removed from map

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL