github.com/haraldrudell/parl@v0.4.176/pmaps/pmaps2/thread-safe-map.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pmaps2
     7  
     8  import (
     9  	"sync"
    10  
    11  	"golang.org/x/exp/maps"
    12  )
    13  
    14  const (
    15  	// with [ThreadSafeMap.Delete] sets the mapping value to the
    16  	// zero-value prior to delete
    17  	SetZeroValue = true
    18  	// with [ThreadSafeMap.Clear], the map is cleared using range
    19  	// and delete of all keys rather than re-created
    20  	RangeDelete = true
    21  )
    22  
    23  // ThreadSafeMap is a thread-safe reusable promotable hash-map
    24  //   - ThreadSafeMap is the innermost type providing thread-safety to consuming
    25  //     map implementations
    26  //   - 5 native Go map functions: Get Put Delete Length Range
    27  //   - — Delete optionally writes zero-value
    28  //   - convenience methods:
    29  //   - — Clone based on [maps.Clone]
    30  //   - — Clear using fast recreate or [maps.Range] optionally writing zero-values
    31  //   - lock control: Lock RLock
    32  //   - order functions:
    33  //   - — List unordered values
    34  //   - ThreadSafeMap uses reader/writer mutual exclusion lock for thread-safety
    35  //   - map mechnic is Go map
    36  type ThreadSafeMap[K comparable, V any] struct {
    37  	lock            *sync.RWMutex
    38  	goMap           map[K]V
    39  	unlock, runlock func()
    40  }
    41  
    42  // NewThreadSafeMap returns a thread-safe Go map
    43  //   - stores self-refencing pointers
    44  func NewThreadSafeMap[K comparable, V any]() (m *ThreadSafeMap[K, V]) {
    45  	var rwm sync.RWMutex
    46  	return &ThreadSafeMap[K, V]{
    47  		lock:    &rwm,
    48  		goMap:   make(map[K]V),
    49  		unlock:  rwm.Unlock,
    50  		runlock: rwm.RUnlock,
    51  	}
    52  }
    53  
    54  // allows consumers to obtain the write lock
    55  //   - returns a function releasing the lock
    56  func (m *ThreadSafeMap[K, V]) Lock() (unlock func()) {
    57  	m.lock.Lock()
    58  	return m.unlock
    59  }
    60  
    61  // allows consumers to obtain the read lock
    62  //   - returns a function releasing the lock
    63  func (m *ThreadSafeMap[K, V]) RLock() (runlock func()) {
    64  	m.lock.RLock()
    65  	return m.runlock
    66  }
    67  
    68  // Get returns the value mapped by key or the V zero-value otherwise.
    69  //   - hasValue is true if a mapping was found
    70  //   - invoked while holding Lock or RLock
    71  //   - O(1)
    72  func (m *ThreadSafeMap[K, V]) Get(key K) (value V, hasValue bool) {
    73  	value, hasValue = m.goMap[key]
    74  	return
    75  }
    76  
    77  // Put creates or replaces a mapping
    78  //   - invoked while holding Lock
    79  func (m *ThreadSafeMap[K, V]) Put(key K, value V) { m.goMap[key] = value }
    80  
    81  // Delete removes mapping for key
    82  //   - if key is not mapped, the map is unchanged
    83  //   - if useZeroValue is [pmaps.SetZeroValue], the mapping value is first
    84  //     set to the zero-value. This prevents temporary memory leaks
    85  //     when V contains pointers to large objects
    86  //   - O(log n)
    87  //   - invoked while holding Lock
    88  func (m *ThreadSafeMap[K, V]) Delete(key K, useZeroValue ...bool) {
    89  
    90  	// if doZero is not present and true, regular map delete
    91  	if len(useZeroValue) == 0 || !useZeroValue[0] {
    92  		delete(m.goMap, key)
    93  		return // non-zero-value delete
    94  	}
    95  
    96  	// if key mapping does not exist: noop
    97  	if _, itemExists := m.goMap[key]; !itemExists {
    98  		return // write-free item does not exist return
    99  	}
   100  
   101  	// set value to zero to prevent temporary memory leaks
   102  	var zeroValue V
   103  	m.goMap[key] = zeroValue
   104  
   105  	// delete
   106  	delete(m.goMap, key)
   107  }
   108  
   109  // Length returns the number of mappings
   110  //   - invoked while holding RLock or Lock
   111  func (m *ThreadSafeMap[K, V]) Length() (length int) { return len(m.goMap) }
   112  
   113  // Range traverses map bindings
   114  //   - iterates over map until rangeFunc returns false
   115  //   - similar to [sync.Map.Range] func (*sync.Map).Range(f func(key any, value any) bool)
   116  //   - invoked while holding RLock or Lock
   117  func (m *ThreadSafeMap[K, V]) Range(rangeFunc func(key K, value V) (keepGoing bool)) (rangedAll bool) {
   118  	for k, v := range m.goMap {
   119  		if !rangeFunc(k, v) {
   120  			return
   121  		}
   122  	}
   123  	return true
   124  }
   125  
   126  // Clear empties the map
   127  //   - if useRange is RangeDelete, the map is cleared by
   128  //     iterating and deleteing all keys
   129  //   - invoked while holding Lock
   130  func (m *ThreadSafeMap[K, V]) Clear(useRange ...bool) {
   131  
   132  	// if useRange is not present and true, clear by re-initialize
   133  	if len(useRange) == 0 || !useRange[0] {
   134  		m.goMap = make(map[K]V)
   135  		return // re-create clear return
   136  	}
   137  
   138  	// zero-out and delete each item
   139  	var zeroValue V
   140  	for k := range m.goMap {
   141  		m.goMap[k] = zeroValue
   142  		delete(m.goMap, k)
   143  	}
   144  }
   145  
   146  // Clone returns a shallow clone of the map
   147  //   - clone is done by ranging all keys
   148  //   - invoked while holding RLock or Lock
   149  func (m *ThreadSafeMap[K, V]) Clone() (clone *ThreadSafeMap[K, V]) {
   150  	var rwm sync.RWMutex
   151  	clone = &ThreadSafeMap[K, V]{
   152  		lock:    &rwm,
   153  		goMap:   maps.Clone(m.goMap),
   154  		unlock:  rwm.Unlock,
   155  		runlock: rwm.RUnlock,
   156  	}
   157  
   158  	return
   159  }
   160  
   161  // List provides the mapped values, undefined ordering
   162  //   - O(n)
   163  //   - invoked while holding RLock or Lock
   164  func (m *ThreadSafeMap[K, V]) List(n int) (list []V) {
   165  
   166  	// handle n
   167  	var length = len(m.goMap)
   168  	if n == 0 {
   169  		n = length
   170  	} else if n > length {
   171  		n = length
   172  	}
   173  
   174  	// create and populate list
   175  	list = make([]V, n)
   176  	i := 0
   177  	for _, v := range m.goMap {
   178  		list[i] = v
   179  		i++
   180  		if i >= n {
   181  			break
   182  		}
   183  	}
   184  
   185  	return
   186  }