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 }