github.com/haraldrudell/parl@v0.4.176/atomic-min.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package parl
     7  
     8  import (
     9  	"sync"
    10  	"sync/atomic"
    11  
    12  	"golang.org/x/exp/constraints"
    13  )
    14  
    15  // AtomicMin is a thread-safe container for a minimum value of any integer type
    16  //   - hasValue indicator
    17  //   - generic for any underlying Integer type
    18  //   - if type is signed, min may be negative
    19  //   - lock for first Value invocation
    20  //   - initialization-free
    21  type AtomicMin[T constraints.Integer] struct {
    22  	isInitialized atomic.Bool   // whether a value is present
    23  	value         atomic.Uint64 // current min value as uint64
    24  	initLock      sync.Mutex    // thread selector and wait for wtriting initial value
    25  }
    26  
    27  // Value notes a new min-candidate
    28  //   - if not a new minima, state is not changed
    29  //   - Thread-safe
    30  func (a *AtomicMin[T]) Value(value T) (isNewMin bool) {
    31  
    32  	// value-valueU64 is candidate min-value
    33  	var valueU64 uint64 = uint64(value)
    34  
    35  	// ensure initialized
    36  	if !a.isInitialized.Load() {
    37  		if isNewMin = a.init(valueU64); isNewMin {
    38  			return // this thread set initial value return
    39  		}
    40  	}
    41  
    42  	// aggregate minimum
    43  	var current = a.value.Load()
    44  	var currentT = T(current)
    45  	// make comparison in T domain
    46  	if isNewMin = value < currentT; !isNewMin {
    47  		return // too large value, nothing to do return
    48  	}
    49  
    50  	// ensure write of new min value
    51  	for {
    52  
    53  		// try to write
    54  		if a.value.CompareAndSwap(current, valueU64) {
    55  			return // min-value updated return
    56  		}
    57  
    58  		// load new copy of value
    59  		current = a.value.Load()
    60  		currentT = T(current)
    61  		if currentT <= value {
    62  			return // ok min-value written by other thread return
    63  		}
    64  	}
    65  }
    66  
    67  // Min returns current minimum value and a flag whether a value is present
    68  //   - Thread-safe
    69  func (a *AtomicMin[T]) Min() (value T, hasValue bool) {
    70  	if hasValue = a.isInitialized.Load(); !hasValue {
    71  		return // no min yet return
    72  	}
    73  	value = T(a.value.Load())
    74  	return
    75  }
    76  
    77  // init uses lock to have loser threads wait until winner thread has updated value
    78  func (a *AtomicMin[T]) init(valueU64 uint64) (didStore bool) {
    79  	a.initLock.Lock()
    80  	defer a.initLock.Unlock()
    81  
    82  	if didStore = !a.isInitialized.Load(); !didStore {
    83  		return // another thread was first
    84  	}
    85  	a.value.Store(valueU64)
    86  	a.isInitialized.Store(true)
    87  	return
    88  }