syncx

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package syncx provides synchronization utilities.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnlockOfUnlockedMutex reports an attempt to unlock an unlocked mutex.
	ErrUnlockOfUnlockedMutex = errors.New("unlock of unlocked reentrant mutex")

	// ErrUnlockFromAnotherGoroutine reports an attempt to unlock a mutex owned by another goroutine.
	ErrUnlockFromAnotherGoroutine = errors.New("unlock from non-owner goroutine")

	// ErrUnlockWithNegativeCount Unlock with negative count.
	ErrUnlockWithNegativeCount = errors.New("unlock with negative count")
)

Functions

func WithLock

func WithLock(mutex sync.Locker, action func())

WithLock executes the given action while holding the provided lock.

It accepts any sync.Locker (e.g., *sync.Mutex, *sync.RWMutex) and a function with no parameters or return values. If the action is nil, nothing is executed.

The lock is guaranteed to be released after the action completes, even if the action panics or returns early.

Types

type ReentrantMutex

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

A ReentrantMutex is a reentrant mutual exclusion lock. The zero value for a ReentrantMutex is an unlocked mutex.

A ReentrantMutex must not be copied after first use.

In the terminology of the Go memory model, the n'th call to ReentrantMutex.Unlock by the owning goroutine "synchronizes before" the m'th call to ReentrantMutex.Lock for any n < m, accounting for recursion levels. ReentrantMutex allows the same goroutine to acquire the lock multiple times without deadlock. It tracks the owning goroutine and recursion level; only the owning goroutine may unlock it, and the mutex is released when the recursion level reaches zero.

ReentrantMutex implements the sync.Locker interface.

func NewReentrantMutex

func NewReentrantMutex() *ReentrantMutex

NewReentrantMutex creates and initializes a new ReentrantMutex.

func (*ReentrantMutex) Lock

func (rm *ReentrantMutex) Lock()

Lock locks rm. If the lock is already held by the current goroutine, the recursion count is incremented. Otherwise, the calling goroutine blocks until the rmutex is available.

func (*ReentrantMutex) Unlock

func (rm *ReentrantMutex) Unlock()

Unlock unlocks rm. It panics if rm is not locked on entry to Unlock.

Unlock must be called by the goroutine that owns the lock. If the recursion count is greater than 1, it is decremented. If the recursion count reaches 0, the lock is released.

A locked ReentrantMutex is associated with a particular goroutine. It is not allowed for one goroutine to lock a ReentrantMutex and then arrange for another goroutine to unlock it.

type Semaphore

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

Semaphore is a counting semaphore that bounds the number of concurrent holders.

The zero value (and a nil *Semaphore) is an unlimited semaphore: all acquire operations succeed immediately and Release is a no-op.

All methods are safe for concurrent use by multiple goroutines.

func NewSemaphore

func NewSemaphore(capacity uint) *Semaphore

NewSemaphore returns a semaphore with the provided capacity.

If capacity <= 0, it returns an unlimited semaphore, for which all acquire operations succeed immediately and Release does nothing.

func (*Semaphore) Acquire

func (s *Semaphore) Acquire()

Acquire obtains one slot from s, blocking until a slot is available. For an unlimited semaphore, Acquire is a no-op.

func (*Semaphore) AcquireContext

func (s *Semaphore) AcquireContext(ctx context.Context) error

AcquireContext attempts to obtain one slot, blocking until a slot is available or the context is canceled or its deadline is exceeded. It returns ctx.Err() if the context is done first. For an unlimited semaphore, AcquireContext returns nil immediately.

func (*Semaphore) Cap

func (s *Semaphore) Cap() int

Cap returns the maximum number of concurrent holders (the capacity). For an unlimited semaphore, Cap returns 0.

func (*Semaphore) InUse

func (s *Semaphore) InUse() int

InUse reports the current number of acquired slots. For an unlimited semaphore, InUse returns 0.

func (*Semaphore) Release

func (s *Semaphore) Release()

Release releases one previously acquired slot. On a limited semaphore, calling Release without a matching acquire panics. On an unlimited semaphore, Release is a no-op.

func (*Semaphore) TryAcquire

func (s *Semaphore) TryAcquire() bool

TryAcquire attempts to obtain one slot without blocking. It returns true if a slot was acquired and false otherwise. For an unlimited semaphore, TryAcquire always returns true.

type SyncValue

type SyncValue[T any] struct {
	// contains filtered or unexported fields
}

SyncValue is a generic wrapper around a value of any type `T` that allows concurrent access with safe read and write operations protected by an RWMutex.

It provides two methods: MutateValue and ReadValue.

  • MutateValue gives exclusive write access to the wrapped value.
  • ReadValue gives shared read access to the wrapped value.

Inside MutateValue, mu.Lock() / mu.Unlock() are used. Inside ReadValue, mu.RLock() / mu.RUnlock() are used.

Example:

var obj ComplexObject

m := NewSyncValue[ComplexObject](ComplexObject{Slice: ..., Map: ...})

// Safe mutation
m.MutateValue(func(v *ComplexObject) {
	v.Slice = append(v.Slice, 100)
})

// Safe read
var safeCopy ComplexObject
m.ReadValue(func(v *ComplexObject) {
	safeCopy = v.DeepCopy() // good way
	// safeCopy = *v // BAD WAY: shallow copy may share internal memory
})

Note:

  • Do NOT store the pointer passed into the callback for later use; it is only valid within the callback.
  • When T is a reference type (e.g., slice, map, pointer, channel), copying `*v` only performs a shallow copy, meaning the underlying data may still be shared. Use DeepCopy or an explicit cloning function to avoid data races.

func NewSyncValue

func NewSyncValue[T any](value ...T) *SyncValue[T]

NewSyncValue constructs a SyncValue initialized with the provided value.

func (*SyncValue[T]) MutateValue

func (sv *SyncValue[T]) MutateValue(f func(v *T))

MutateValue provides exclusive, write access to the wrapped value by invoking the supplied function while holding an exclusive lock. The callback receives a pointer to the internal value, allowing in-place updates.

IMPORTANT:

  • Do not store the pointer beyond the duration of the callback.
  • Avoid calling other methods of SyncValue from within the callback to prevent lock re-entrancy issues (Go mutexes are not re-entrant).

Example (mutating a struct field and a slice in-place):

type Config struct {
    Enabled bool
    Items   []int
}

cfg := NewSyncValue(Config{Enabled: false, Items: []int{1, 2}})
cfg.MutateValue(func(v *Config) {
    v.Enabled = true
    v.Items = append(v.Items, 3)
})

func (*SyncValue[T]) ReadValue

func (sv *SyncValue[T]) ReadValue(f func(v *T))

ReadValue provides shared, read access to the wrapped value by invoking the supplied function while holding a shared lock. The callback receives a pointer to the internal value for efficient inspection.

IMPORTANT:

  • The pointer is only safe to use within the callback.
  • If T (or its fields) contains reference types (e.g., slices/maps), copying *v will be shallow. To safely use the value after the callback returns, make a defensive deep copy.

Example (creating a defensive copy of a slice):

sv := NewSyncValue([]int{1, 2, 3})
var snapshot []int
sv.ReadValue(func(v *[]int) {
	snapshot = make([]int, len(*v)) // copy does't alloc memory!! You should call make for it.
	copy(snapshot, *v) // Defensive copy: underlying array is not shared.
})

Jump to

Keyboard shortcuts

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