atomic

package
v1.19.2 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 3 Imported by: 0

README

Atomic Package

Go Version

Generic, thread-safe atomic primitives for Go with type-safe operations and zero-allocation performance.


Table of Contents


Overview

This package provides production-ready, generic atomic primitives that extend Go's standard sync/atomic package with type safety and developer-friendly APIs. It eliminates the need for type assertions while maintaining zero-allocation performance.

Design Philosophy
  1. Type Safety: Generic APIs eliminate runtime type assertions
  2. Zero Allocation: Lock-free operations with no memory overhead
  3. Standard Library First: Built on sync.atomic and sync.Map
  4. Simple APIs: Intuitive interfaces matching Go conventions
  5. Race-Free: All operations validated with -race detector

Key Features

  • Generic Atomic Values: Type-safe Store, Load, Swap, CompareAndSwap operations
  • Thread-Safe Maps: Both generic (Map[K]) and typed (MapTyped[K,V]) concurrent maps
  • Default Values: Configurable defaults for load/store operations
  • Safe Type Casting: Utility functions for runtime type conversions
  • Zero Overhead: No allocations for most operations
  • Race Tested: Comprehensive concurrency validation

Installation

go get github.com/nabbar/golib/atomic

Architecture

Package Structure
atomic/
├── value.go           # Generic atomic value implementation
├── mapany.go          # Map with any values (generic keys)
├── synmap.go          # Typed map implementation
├── cast.go            # Type casting utilities
├── default.go         # Default value management
└── interface.go       # Public interfaces
Type System
┌─────────────────────────────────────────────┐
│              Atomic Package                  │
└──────────────┬──────────────┬────────────────┘
               │              │
      ┌────────▼─────┐  ┌────▼──────────┐
      │   Value[T]   │  │    Maps       │
      │              │  │               │
      │ Store        │  │ Map[K]        │
      │ Load         │  │ MapTyped[K,V] │
      │ Swap         │  │               │
      │ CompareSwap  │  │ Range, Delete │
      └──────────────┘  └───────────────┘
Core Components
Component Purpose Thread-Safe
Value[T] Generic atomic value container
Map[K] Map with typed keys, any values
MapTyped[K,V] Fully typed concurrent map
Cast[T] Safe type conversion utilities N/A

Quick Start

Atomic Value

Type-safe atomic value operations:

package main

import (
    "fmt"
    "github.com/nabbar/golib/atomic"
)

type Config struct {
    Timeout int
    Enabled bool
}

func main() {
    // Create typed atomic value
    val := atomic.NewValue[Config]()
    
    // Store value
    val.Store(Config{Timeout: 30, Enabled: true})
    
    // Load value (type-safe, no assertions)
    cfg := val.Load()
    fmt.Println(cfg.Timeout) // Output: 30
    
    // Compare-and-swap
    old := Config{Timeout: 30, Enabled: true}
    new := Config{Timeout: 60, Enabled: true}
    swapped := val.CompareAndSwap(old, new)
    fmt.Println(swapped) // Output: true
    
    // Swap and return old value
    prev := val.Swap(Config{Timeout: 90, Enabled: false})
    fmt.Println(prev.Timeout) // Output: 60
}
Default Values

Configure default values for load/store operations:

val := atomic.NewValue[int]()

// Set default for empty loads
val.SetDefaultLoad(0)

// Set default to replace specific store values
val.SetDefaultStore(-1)

val.Store(0) // Actually stores -1
v := val.Load() // Returns 0 if empty
Atomic Map (Generic Keys, Any Values)
package main

import "github.com/nabbar/golib/atomic"

func main() {
    // Create map with string keys, any values
    m := atomic.NewMapAny[string]()
    
    // Store different value types
    m.Store("count", 123)
    m.Store("name", "test")
    
    // Load with type assertion
    count, ok := m.Load("count")
    if ok {
        // count is interface{}, needs assertion
        num := count.(int)
    }
    
    // Delete
    m.Delete("count")
}
Typed Atomic Map
package main

import "github.com/nabbar/golib/atomic"

func main() {
    // Create fully typed map
    cache := atomic.NewMapTyped[string, int]()
    
    // Type-safe operations (no assertions needed)
    cache.Store("user:1", 100)
    cache.Store("user:2", 200)
    
    // Load returns typed value
    score, ok := cache.Load("user:1")
    if ok {
        // score is int, no assertion needed
        fmt.Println(score) // Output: 100
    }
    
    // LoadOrStore
    actual, loaded := cache.LoadOrStore("user:3", 300)
    
    // Range with typed callback
    cache.Range(func(key string, value int) bool {
        fmt.Printf("%s: %d\n", key, value)
        return true // continue
    })
}
Safe Type Casting
package main

import "github.com/nabbar/golib/atomic"

func main() {
    var anyVal interface{} = 42
    
    // Safe cast with boolean return
    num, ok := atomic.Cast[int](anyVal)
    if ok {
        fmt.Println(num) // Output: 42
    }
    
    // Check if empty/nil
    empty := atomic.IsEmpty[string](anyVal)
    fmt.Println(empty) // Output: true (not a string)
}

Performance

Memory Characteristics
  • Atomic Value: 16 bytes (pointer + interface)
  • Map Entry: ~48 bytes per key-value pair
  • Zero Allocations: Load, Store, Swap operations don't allocate
Throughput Benchmarks
Operation Latency Allocations Notes
Value.Store ~15ns 0 Lock-free
Value.Load ~5ns 0 Lock-free
Value.CompareAndSwap ~20ns 0 Lock-free
Map.Store ~100ns 1 First store only
Map.Load ~50ns 0 Read-optimized
Map.Delete ~80ns 0 Amortized
Map.Range (100 items) ~5μs 0 Sequential

Measured on AMD64, Go 1.21

Concurrency Scalability
Goroutines:        10      50      100     500
Value Ops/sec:   100M    95M     90M     85M
Map Ops/sec:      20M    18M     16M     14M

Note: Linear degradation under high contention

Performance Characteristics

  • Lock-Free Values: No mutex overhead for atomic values
  • Read-Optimized Maps: Fast reads, slightly slower writes
  • No Allocations: Most operations don't allocate memory
  • Cache-Friendly: Minimal false sharing

Use Cases

This package is designed for high-performance concurrent programming scenarios:

Configuration Management

  • Hot-reloadable configuration without locks
  • Thread-safe config updates with atomic swaps
  • Default values for missing configurations

Caching

  • High-read, low-write concurrent caches
  • Type-safe cache entries
  • Lock-free cache lookups

Counters & Metrics

  • Lock-free performance counters
  • Concurrent metric aggregation
  • Atomic stat tracking

Registry Pattern

  • Thread-safe service registries
  • Handler registrations
  • Dynamic plugin systems

State Management

  • Concurrent state machines
  • Feature flags
  • Circuit breakers

API Reference

Value[T] Interface

Generic atomic value container:

type Value[T any] interface {
    // Set default value returned by Load when empty
    SetDefaultLoad(def T)
    
    // Set default value that replaces specific values in Store
    SetDefaultStore(def T)
    
    // Load returns the current value (type-safe)
    Load() (val T)
    
    // Store sets the value atomically
    Store(val T)
    
    // Swap sets new value and returns old
    Swap(new T) (old T)
    
    // CompareAndSwap updates if current equals old
    CompareAndSwap(old, new T) (swapped bool)
}

Constructor: NewValue[T any]() Value[T]

Map[K] Interface

Concurrent map with typed keys, any values:

type Map[K comparable] interface {
    Load(key K) (value any, ok bool)
    Store(key K, value any)
    LoadOrStore(key K, value any) (actual any, loaded bool)
    LoadAndDelete(key K) (value any, loaded bool)
    Delete(key K)
    Swap(key K, value any) (previous any, loaded bool)
    CompareAndSwap(key K, old, new any) bool
    CompareAndDelete(key K, old any) (deleted bool)
    Range(f func(key K, value any) bool)
}

Constructor: NewMapAny[K comparable]() Map[K]

MapTyped[K,V] Interface

Fully typed concurrent map:

type MapTyped[K comparable, V any] interface {
    Load(key K) (value V, ok bool)
    Store(key K, value V)
    LoadOrStore(key K, value V) (actual V, loaded bool)
    LoadAndDelete(key K) (value V, loaded bool)
    Delete(key K)
    Swap(key K, value V) (previous V, loaded bool)
    CompareAndSwap(key K, old, new V) bool
    CompareAndDelete(key K, old V) (deleted bool)
    Range(f func(key K, value V) bool)
}

Constructor: NewMapTyped[K comparable, V any]() MapTyped[K,V]

Type Casting Utilities
// Cast safely converts any value to target type
func Cast[T any](v any) (T, bool)

// IsEmpty checks if value is nil or empty
func IsEmpty[T any](v any) bool

See GoDoc for complete API.


Best Practices

Use Type-Safe APIs

// ✅ Good: Type-safe
cache := atomic.NewMapTyped[string, int]()
val, ok := cache.Load("key") // val is int

// ❌ Avoid: Type assertions
cache := atomic.NewMapAny[string]()
val, ok := cache.Load("key")
num := val.(int) // Runtime panic risk

Consistent Value Types

// ✅ Good: Same type always
var cfg atomic.Value[*Config]
cfg.Store(&Config{})
cfg.Store(&Config{}) // OK

// ❌ Bad: Changing types panics
var v atomic.Value[any]
v.Store("string")
v.Store(123) // Panic!

Check Return Values

// ✅ Good: Check success
val, ok := cache.Load("key")
if !ok {
    // Handle missing key
}

// ❌ Bad: Ignore status
val, _ := cache.Load("key")
use(val) // May be zero value

Use CompareAndSwap Correctly

// ✅ Good: Retry loop
for {
    old := val.Load()
    new := transform(old)
    if val.CompareAndSwap(old, new) {
        break
    }
}

// ❌ Bad: Single attempt
old := val.Load()
new := transform(old)
val.CompareAndSwap(old, new) // May fail

Prefer Atomic Over Mutex for Simple Values

// ✅ Good: Lock-free
var count atomic.Value[int64]
count.Store(count.Load() + 1)

// ❌ Overkill: Mutex
var mu sync.Mutex
var count int64
mu.Lock()
count++
mu.Unlock()

Testing

Test Suite: 100+ specs using Ginkgo v2 and Gomega

# Run tests
go test ./...

# With race detection (required)
CGO_ENABLED=1 go test -race ./...

# With coverage
go test -cover ./...

Quality Assurance

  • ✅ Zero data races (verified with -race)
  • ✅ Thread-safe concurrent operations
  • ✅ Comprehensive edge case coverage
  • ✅ >95% code coverage

See TESTING.md for detailed testing documentation.


Contributing

Contributions are welcome! Please follow these guidelines:

Code Contributions

  • Do not use AI to generate package implementation code
  • AI may assist with tests, documentation, and bug fixing
  • All contributions must pass go test -race
  • Maintain or improve test coverage (>95%)
  • Follow existing code style and patterns

Testing Requirements

  • Test concurrent access patterns
  • Verify with race detector
  • Test edge cases (nil, zero values, high contention)
  • Add benchmarks for performance-critical code

Pull Requests

  • Provide clear description of changes
  • Include test results with -race
  • Update documentation
  • Ensure backward compatibility

Future Enhancements

Potential improvements for future versions:

Additional Atomic Types

  • atomic.Bool (wrapper around atomic.Value[bool])
  • atomic.Duration (typed duration handling)
  • atomic.Pointer[T] (typed pointer operations)

Enhanced Map Operations

  • Len() method for map size
  • Clear() method to remove all entries
  • Filtered range with predicates
  • Bulk operations (StoreAll, DeleteAll)

Performance Optimizations

  • Padding to prevent false sharing
  • NUMA-aware allocation strategies
  • Custom hash functions for map keys

Utilities

  • Atomic slice operations
  • Copy-on-write data structures
  • Lock-free queues and stacks

Suggestions welcome via GitHub issues.


AI Transparency Notice

In accordance with Article 50.4 of the EU AI Act, AI assistance has been used for testing, documentation, and bug fixing under human supervision.


License

MIT License - See LICENSE file for details.


Resources

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cast

func Cast[M any](src any) (model M, casted bool)

Cast attempts to safely convert any value to the target type M. Returns the converted value and true if successful, or the zero value of M and false if conversion fails.

The function performs two checks:

  1. Deep equality check to detect if src is already the zero value of M
  2. Type assertion to convert src to M

This is particularly useful when working with atomic.Value which stores values as interface{}.

Example:

var v atomic.Value
v.Store(42)
num, ok := Cast[int](v.Load())  // num = 42, ok = true
str, ok := Cast[string](v.Load())  // str = "", ok = false

func IsEmpty

func IsEmpty[M any](src any) bool

IsEmpty checks if the source value is nil, zero, or cannot be cast to type M. Returns true if the value is empty or if Cast[M] fails, false otherwise.

This is useful for validating values before storing them in atomic containers.

Example:

empty := IsEmpty[string](nil)           // true
empty := IsEmpty[string]("")            // false (empty string is a valid string)
empty := IsEmpty[string](42)            // true (cannot cast int to string)
empty := IsEmpty[int](0)                // false (0 is a valid int)

Types

type Map

type Map[K comparable] interface {
	// Load returns the value stored in the underlying store for this Map.
	// If no value is present, ok is false.
	// Otherwise, ok is true and the value is returned.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, ok := m.Load("key")
	//  fmt.Println(val, ok) // prints "value true"
	Load(key K) (value any, ok bool)
	// Store atomically stores the given value in the underlying store for this Map.
	// It overwrites any existing value stored in the underlying store for the given key.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, ok := m.Load("key")
	//  fmt.Println(val, ok) // prints "value true"
	Store(key K, value any)

	// LoadOrStore atomically loads the value stored in the underlying store for this Map with the given key.
	// If no value is present, it atomically stores the given value in the underlying store for this Map,
	// and returns the given value along with true for the loaded flag.
	// If a value is present, it returns the present value along with true for the loaded flag.
	// Note: If the given value is zero, the default store value (set by SetDefaultStore) is used for storing.
	//
	// Example:
	//  m := NewMap[string]()
	//  val, loaded := m.LoadOrStore("key", "value")
	//  fmt.Println(val, loaded) // prints "value true"
	LoadOrStore(key K, value any) (actual any, loaded bool)
	// LoadAndDelete atomically loads and deletes the value stored in the underlying store for this Map.
	// It returns the value stored in the underlying store for the given key, along with true if the value was present.
	// If the value was not present, the default load value (set by SetDefaultLoad) is returned, and the loaded flag is false.
	//
	// Note: If the value is not zero, the underlying store will be updated with the new value.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, loaded := m.LoadAndDelete("key")
	//  fmt.Println(val, loaded) // prints "value true"
	LoadAndDelete(key K) (value any, loaded bool)

	// Delete atomically deletes the value stored in the underlying store for this Map with the given key.
	// It returns true if the value was present, and false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  deleted := m.Delete("key")
	//  fmt.Println(deleted) // prints "true"
	Delete(key K)
	// Swap atomically swaps the value stored in the underlying store for this Map with the given key
	// with the given value. It returns the previous value stored in the underlying store for the given
	// key, and a boolean indicating whether the swap was successful.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "old")
	//  prev, swapped := m.Swap("key", "new")
	//  fmt.Println(prev, swapped) // prints "old true"
	Swap(key K, value any) (previous any, loaded bool)

	// CompareAndSwap atomically compares the value stored in the underlying store for this Map with the given key
	// with the given old value. If they are equal, it atomically swaps the value with the given new value.
	// It returns true if the swap was successful, or false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "old")
	//  swapped := m.CompareAndSwap("key", "old", "new")
	//  fmt.Println(swapped) // prints "true"
	CompareAndSwap(key K, old, new any) bool
	// CompareAndDelete atomically compares the value stored in the underlying store for this Map with the given key
	// with the given old value. If they are equal, it atomically deletes the value from the underlying store.
	// It returns true if the value was present and deleted, and false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  deleted := m.CompareAndDelete("key", "value")
	//  fmt.Println(deleted) // prints "true"
	CompareAndDelete(key K, old any) (deleted bool)

	// Range calls the given function for each key in the underlying store.
	// It calls the function in an unspecified order, and will stop calling the function
	// if the function returns false.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  m.Range(func(k string, v any) bool {
	//      fmt.Println(k, v)
	//      return true
	//  })
	//  // prints "key value"
	Range(f func(key K, value any) bool)
}

func NewMapAny

func NewMapAny[K comparable]() Map[K]

NewMapAny returns a new Map with the given key type. It uses a sync.Map as the underlying store.

Example:

m := NewMapAny[int]()
// m is a Map with key type int and underlying store sync.Map{}.

type MapTyped

type MapTyped[K comparable, V any] interface {
	// Load returns the value stored in the underlying store for this Map with the given key.
	// If no value is present, ok is false.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, ok := m.Load("key")
	//  fmt.Println(val, ok) // prints "value true"
	Load(key K) (value V, ok bool)
	// Store atomically stores the given value in the underlying store for this Map with the given key.
	// It overwrites any existing value stored in the underlying store for the given key.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, ok := m.Load("key")
	//  fmt.Println(val, ok) // prints "value true"
	Store(key K, value V)

	// LoadOrStore atomically loads the value stored in the underlying store for this Map with the given key.
	// If no value is present, it atomically stores the given value in the underlying store for this Map,
	// and returns the given value along with true for the loaded flag.
	// If a value is present, it returns the present value along with true for the loaded flag.
	//
	// Note: If the given value is zero, the default store value (set by SetDefaultStore) is used for storing.
	//
	// Example:
	//  m := NewMap[string]()
	//  val, loaded := m.LoadOrStore("key", "value")
	//  fmt.Println(val, loaded) // prints "value true"
	LoadOrStore(key K, value V) (actual V, loaded bool)
	// LoadAndDelete atomically loads and deletes the value stored in the underlying store for this Map with the given key.
	// It returns the value stored in the underlying store for the given key, along with true if the value was present.
	// If the value was not present, the default load value (set by SetDefaultLoad) is returned, and the loaded flag is false.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  val, loaded := m.LoadAndDelete("key")
	//  fmt.Println(val, loaded) // prints "value true"
	LoadAndDelete(key K) (value V, loaded bool)

	// Delete atomically deletes the value stored in the underlying store for this Map with the given key.
	// It returns true if the value was present, and false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  deleted := m.Delete("key")
	//  fmt.Println(deleted) // prints "true"
	Delete(key K)
	// Swap atomically swaps the value stored in the underlying store for this Map with the given key
	// with the given new value. It returns the previous value stored in the underlying store,
	// along with true if the swap was successful, or false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "old")
	//  val, loaded := m.Swap("key", "new")
	//  fmt.Println(val, loaded) // prints "old true"
	Swap(key K, value V) (previous V, loaded bool)

	// CompareAndSwap atomically compares the value stored in the underlying store for this Map with the given key
	// with the given old value. If they are equal, it atomically swaps the value with the given new value.
	// It returns true if the swap was successful, or false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "old")
	//  swapped := m.CompareAndSwap("key", "old", "new")
	//  fmt.Println(swapped) // prints "true"
	CompareAndSwap(key K, old, new V) bool
	// CompareAndDelete atomically compares the value stored in the underlying store for this Map with the given key
	// with the given old value. If they are equal, it atomically deletes the value from the underlying store.
	// It returns true if the value was present and deleted, and false otherwise.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "old")
	//  deleted := m.CompareAndDelete("key", "old")
	//  fmt.Println(deleted) // prints "true"
	CompareAndDelete(key K, old V) (deleted bool)

	// Range calls the given function for each key in the underlying store.
	// It calls the function in an unspecified order, and will stop calling the function
	// if the function returns false.
	//
	// Example:
	//  m := NewMap[string]()
	//  m.Store("key", "value")
	//  m.Range(func(k string, v any) bool {
	//      fmt.Println(k, v)
	//      return true
	//  })
	//  // prints "key value"
	Range(f func(key K, value V) bool)
}

func NewMapTyped

func NewMapTyped[K comparable, V any]() MapTyped[K, V]

NewMapTyped returns a new Map with the given key type and value type. It uses a sync.Map as the underlying store.

Example:

m := NewMapTyped[int, string]()
// m is a Map with key type int and value type string, and underlying store sync.Map{}.

type Value

type Value[T any] interface {
	// SetDefaultLoad sets the default load value for this Value.
	// The default value is returned when Load is called and the value is not present in the underlying store.
	//
	// Note: SetDefaultLoad should be called before first use of Load.
	SetDefaultLoad(def T)
	// SetDefaultStore sets the default store value for this Value.
	// The default value is used when Store is called with a value of zero.
	// Note: SetDefaultStore should be called before first use of Store.
	SetDefaultStore(def T)

	// Load returns the value stored in the underlying store for this Value.
	// If no value is present, the default load value (set by SetDefaultLoad) is returned.
	// Note: Load will return the default load value until the first successful call to Store.
	Load() (val T)
	// Store sets the value for the given key in the underlying store for this Value.
	// Note: Store will use the default store value (set by SetDefaultStore) if the value passed is zero.
	//
	// If the value is not zero, the underlying store will be updated with the new value.
	//
	// Example:
	//  v := NewValue[int]()
	//  v.SetDefaultStore(42)
	//  v.Store(0) // sets 42 as the value in the underlying store
	//  v.Store(99) // sets 99 as the value in the underlying store
	Store(val T)
	// Swap atomically swaps the value of the underlying store for this Value with the given new value.
	// It returns the previous value stored in the underlying store.
	// If the previous value is zero, the default store value (set by SetDefaultStore) is returned.
	//
	// Example:
	//  v := NewValue[int]()
	//  v.SetDefaultStore(42)
	//  old, _ := v.Swap(0) // old is 42
	Swap(new T) (old T)
	// CompareAndSwap atomically compares the value stored in the underlying store for this Value
	// with the given old value. If they are equal, it atomically swaps the value with the given new value.
	// It returns true if the swap was successful, or false otherwise.
	//
	// Note: If the old value is zero, the default store value (set by SetDefaultStore) is used for comparison.
	// If the new value is zero, the default store value (set by SetDefaultStore) is used for swapping.
	//
	// Example:
	//  v := NewValue[int]()
	//  v.SetDefaultStore(42)
	//  swapped := v.CompareAndSwap(0, 99) // swapped is true, and the value in the underlying store is 99
	CompareAndSwap(old, new T) (swapped bool)
}

func NewValue

func NewValue[T any]() Value[T]

NewValue returns a new Value with the given type. The default load value is the zero value of the given type, and the default store value is the zero value of the given type.

Example:

v := NewValue[int]()
// v is a Value with default load value 0 and default store value 0.

func NewValueDefault

func NewValueDefault[T any](load, store T) Value[T]

NewValueDefault returns a new Value with the given type, default load value, and default store value. The default load value is the value passed to the load parameter, and the default store value is the value passed to the store parameter.

Example:

v := NewValueDefault[int](0, 42)
// v is a Value with default load value 0 and default store value 42.

Jump to

Keyboard shortcuts

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