nsync package - github.com/vburenin/nsync - Go Packages

nsync

package module
v0.0.0-...-6e6bc42 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 5 Imported by: 17

README

nsync

Go Reference

Synchronization primitives for Go: timed locks, named locks, semaphores, a bounded goroutine executor, and an atomic flag with an optional external lock.

Requires Go 1.27 or later. The development toolchain is Go 1.27.1. The package uses only the standard library. Synchronization uses atomic fast paths and condition variables, with no channels in the library implementation. The default backend is portable Go; compiler intrinsics emit native atomic instructions on ARM, x86, and other Go architectures.

go get github.com/vburenin/nsync

TryMutex

Create a mutex with NewTryMutex(). It provides Lock, Unlock, TryLock, and TryLockTimeout(time.Duration). The try methods return whether the lock was acquired. Unlocking an unlocked mutex panics. Copies of a constructed TryMutex share the same lock.

Contended mutexes adaptively hand ownership to waiting goroutines after extended waiting. They do not guarantee FIFO order or a strict acquisition-latency bound.

For ordinary locks without a timeout, the standard library's sync.Mutex also provides TryLock.

NamedMutex

NamedMutex acquires independent locks by string name. Its zero value is ready to use; NewNamedMutex() is also available.

It provides Lock(name), Unlock(name), TryLock(name), and TryLockTimeout(name, timeout). Unlocking an unknown or unlocked name panics. Created locks are retained for the lifetime of the instance, so use a bounded set of names.

Semaphore

NewSemaphore(capacity) limits concurrent acquisitions. Capacity must be positive; zero or negative capacities panic.

It provides Acquire, Release, TryAcquire, TryAcquireTimeout, and Value. Value reports the number of occupied slots. Releasing without an acquisition panics. Do not copy a semaphore after first use.

All timed acquisition methods try immediately before waiting. Zero or negative timeouts perform a single nonblocking attempt.

OnceMutex and NamedOnceMutex

OnceMutex.Lock() returns true for the first acquisition. Other calls block until Unlock, then return false. Only the successful caller should unlock it. Its zero value is ready to use.

NamedOnceMutex maintains an independent OnceMutex per key. Its zero value is ready to use. Keys must be comparable and equal to themselves (avoid NaN keys). Unlock(key) discards the completed mutex, so a later Lock(key) starts a new cycle. Unlocking an unknown key does nothing. This can combine overlapping cache refreshes for the same key into a single operation.

ControlWaitGroup

NewControlWaitGroup(poolSize) limits the number of tasks running concurrently. The pool size must be positive. Do(func()) blocks until a slot is available, then launches the task and returns true. Wait() waits for running tasks and pending submissions to finish.

workers := nsync.NewControlWaitGroup(4)
for _, job := range jobs {
    workers.Do(func() { process(job) })
}
workers.Wait()

Abort() permanently rejects new tasks and unblocks pending Do calls, which return false. Already admitted tasks may continue running; call Wait to wait for them. Repeated calls to Abort are safe.

As with sync.WaitGroup, submission to an empty group must precede Wait. Before reusing a group, wait for all previous Wait calls to return. Working and Waiting are snapshots for monitoring, not synchronization.

SyncFlag

SyncFlag has an unset zero value. Set and Unset serialize writes with its embedded mutex; IsSet and IsUnset read atomically. Hold Lock to prevent changes, then call Unlock to allow changes again. Calling Set or Unset while holding that mutex would deadlock.

Development

go test -race -shuffle=on ./...
go vet ./...
./benchmarks/cross-build.sh

See performance measurements and design decisions for the frozen baseline, reproducible benchmarks, assembly experiments, allocation measurements, and throughput/latency tradeoffs. Native results are included for Apple M3 Max ARM64 and AMD Ryzen 9 5950X on Linux.

Documentation

Overview

Package nsync provides timed locks, named locks, semaphores, a bounded goroutine executor, and an atomic flag with an external lock.

Acquisitions use atomic fast paths and condition variables for parking, without channels. Contended mutexes adaptively hand ownership to waiting goroutines to limit repeated barging; FIFO order is not guaranteed.

NamedMutex retains a lock for each string name. NamedOnceMutex combines overlapping operations for a comparable key and removes completed operations. Semaphore bounds concurrent acquisitions, while ControlWaitGroup bounds concurrently running functions and supports canceling pending submissions.

TryMutex, Semaphore, and ControlWaitGroup require their constructors. The zero values of NamedMutex, OnceMutex, NamedOnceMutex, and SyncFlag are usable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ControlWaitGroup

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

ControlWaitGroup runs tasks with a limit on concurrent goroutines. Use NewControlWaitGroup to initialize it. A ControlWaitGroup must not be copied after first use.

func NewControlWaitGroup

func NewControlWaitGroup(poolSize int) *ControlWaitGroup

NewControlWaitGroup creates a group. It panics unless poolSize is positive.

func (*ControlWaitGroup) Abort

func (cwg *ControlWaitGroup) Abort()

Abort unblocks pending Do calls and permanently rejects new tasks. Already admitted tasks may continue running; use Wait to wait for them. Repeated calls are safe.

func (*ControlWaitGroup) Do

func (cwg *ControlWaitGroup) Do(userFunc func()) bool

Do waits for a free slot and starts userFunc in a new goroutine, returning true. If the group is aborted before admission, Do returns false without running userFunc. Submission to an empty group must precede Wait.

func (*ControlWaitGroup) Wait

func (cwg *ControlWaitGroup) Wait()

Wait waits for all admitted tasks and pending Do calls to finish. Submission to an empty group must precede Wait. Before reusing a group, all previous Wait calls must have returned.

func (*ControlWaitGroup) Waiting

func (cwg *ControlWaitGroup) Waiting() int

Waiting returns a snapshot of the number of pending Do calls.

func (*ControlWaitGroup) Working

func (cwg *ControlWaitGroup) Working() int

Working returns a snapshot of the number of occupied worker slots.

type NamedMutex

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

NamedMutex provides independent locks by name. The zero value is ready to use. A NamedMutex must not be copied after first use. Locks are retained for the lifetime of the NamedMutex, so the set of names should be bounded.

func NewNamedMutex

func NewNamedMutex() *NamedMutex

NewNamedMutex creates a named mutex.

func (*NamedMutex) Lock

func (nm *NamedMutex) Lock(name string)

Lock acquires the named lock, creating it if necessary.

func (*NamedMutex) TryLock

func (nm *NamedMutex) TryLock(name string) bool

TryLock tries to acquire the named lock without waiting.

func (*NamedMutex) TryLockTimeout

func (nm *NamedMutex) TryLockTimeout(name string, timeout time.Duration) bool

TryLockTimeout tries immediately, then waits up to timeout for the named lock. A non-positive timeout is equivalent to TryLock.

func (*NamedMutex) Unlock

func (nm *NamedMutex) Unlock(name string)

Unlock releases the named lock. It panics if the name is unknown or unlocked.

type NamedOnceMutex

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

NamedOnceMutex combines overlapping operations for the same comparable key. One caller's Lock returns true; concurrent callers wait for its Unlock and return false. After Unlock, a new call may start another operation for the key. The zero value is ready to use and must not be copied after first use. Keys must be comparable and equal to themselves (NaN keys are unsupported).

func NewNamedOnceMutex

func NewNamedOnceMutex() *NamedOnceMutex

NewNamedOnceMutex creates a named once mutex.

func (*NamedOnceMutex) Lock

func (nom *NamedOnceMutex) Lock(key any) bool

Lock starts an operation for key, or waits for the current operation to finish. Only callers receiving true should call Unlock.

func (*NamedOnceMutex) Unlock

func (nom *NamedOnceMutex) Unlock(key any)

Unlock completes the active operation for key. Unknown keys are ignored. Only unshared entries can be recycled; a shared entry remains alive through the references held by its waiters, independently of later operations.

type OnceMutex

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

OnceMutex admits one operation. The first Lock returns true; other calls wait for Unlock and return false. Its zero value is ready to use. It must not be copied after first use.

func NewOnceMutex

func NewOnceMutex() *OnceMutex

NewOnceMutex creates a once mutex.

func (*OnceMutex) Lock

func (om *OnceMutex) Lock() bool

Lock starts the operation or waits for the successful caller's Unlock.

func (*OnceMutex) Unlock

func (om *OnceMutex) Unlock()

Unlock completes the operation and publishes its writes to waiting callers. It panics unless Lock has succeeded and Unlock has not been called before.

type Semaphore

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

Semaphore limits concurrent acquisitions. Use NewSemaphore to initialize it. A Semaphore must not be copied after first use.

func NewSemaphore

func NewSemaphore(value int) *Semaphore

NewSemaphore creates a semaphore. It panics unless value is positive.

func (*Semaphore) Acquire

func (s *Semaphore) Acquire()

Acquire acquires a slot, blocking when all slots are occupied.

func (*Semaphore) Release

func (s *Semaphore) Release()

Release releases a slot. It panics if no slot is occupied.

func (*Semaphore) TryAcquire

func (s *Semaphore) TryAcquire() bool

TryAcquire acquires an available slot without waiting.

func (*Semaphore) TryAcquireTimeout

func (s *Semaphore) TryAcquireTimeout(d time.Duration) bool

TryAcquireTimeout tries immediately, then waits up to d for a slot. A non-positive duration is equivalent to TryAcquire.

func (*Semaphore) Value

func (s *Semaphore) Value() int

Value returns a snapshot of the number of occupied slots.

type SyncFlag

type SyncFlag struct {
	sync.Mutex
	// contains filtered or unexported fields
}

SyncFlag implements a boolean flag that can be set or unset atomically. During set/unset SyncFlag locks the mutex, so if anything needs to prevent a flag from being set/unset should acquire a lock. The zero value is unset. A SyncFlag must not be copied after first use. Set and Unset must not be called while the caller holds the embedded mutex.

func (*SyncFlag) IsSet

func (bf *SyncFlag) IsSet() bool

IsSet atomically checks if flag is set.

func (*SyncFlag) IsUnset

func (bf *SyncFlag) IsUnset() bool

IsUnset atomically checks if flag is unset.

func (*SyncFlag) Set

func (bf *SyncFlag) Set()

Set locks the mutex, sets the flag and unlocks the mutex.

func (*SyncFlag) Unset

func (bf *SyncFlag) Unset()

Unset locks the mutex, resets the flag and unlocks the mutex.

type TryMutex

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

TryMutex provides blocking, nonblocking, and timed lock acquisition. Use NewTryMutex to initialize it. Copies refer to the same underlying lock.

func NewTryMutex

func NewTryMutex() *TryMutex

NewTryMutex creates an unlocked mutex.

func (TryMutex) Lock

func (tm TryMutex) Lock()

Lock acquires the mutex, blocking if it is already locked.

func (TryMutex) TryLock

func (tm TryMutex) TryLock() bool

TryLock acquires the mutex without waiting, returning true on success.

func (TryMutex) TryLockTimeout

func (tm TryMutex) TryLockTimeout(timeout time.Duration) bool

TryLockTimeout tries immediately, then waits up to timeout for the mutex. A non-positive timeout is equivalent to TryLock.

func (TryMutex) Unlock

func (tm TryMutex) Unlock()

Unlock releases the mutex. It panics if the mutex is unlocked.

Directories

Path Synopsis
benchmarks
asm
Package asmbench compares handwritten assembly with compiler intrinsics.
Package asmbench compares handwritten assembly with compiler intrinsics.
internal
baseline
Frozen pre-optimization implementation.
Frozen pre-optimization implementation.

Jump to

Keyboard shortcuts

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