github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/cmap.go (about) 1 package f 2 3 import "sync" 4 5 // CMapShardCount Map Shard Count 6 var CMapShardCount = 32 7 8 // CMap thread safe map of type string:Anything. 9 // To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. 10 type CMap []*CMapShared 11 12 // CMapShared thread safe string to anything map. 13 type CMapShared struct { 14 items map[string]interface{} 15 sync.RWMutex // Read Write mutex, guards access to internal map. 16 } 17 18 // NewConcurrentMap Creates a new concurrent map. 19 func NewConcurrentMap() CMap { 20 m := make(CMap, CMapShardCount) 21 for i := 0; i < CMapShardCount; i++ { 22 m[i] = &CMapShared{items: make(map[string]interface{})} 23 } 24 return m 25 } 26 27 // NewConcurrentMapFromJSON Creates a new concurrent map. 28 func NewConcurrentMapFromJSON(json []byte) (m CMap, err error) { 29 tmp := make(map[string]interface{}) 30 err = DecodeJson(json, &tmp) 31 if err == nil { 32 m = NewConcurrentMap() 33 m.MSet(tmp) 34 } 35 return 36 } 37 38 // GetShard returns shard under given key. 39 func (m CMap) GetShard(key string) *CMapShared { 40 return m[uint(fnv32(key))%uint(CMapShardCount)] 41 } 42 43 // MSet sets values. 44 func (m CMap) MSet(data map[string]interface{}) { 45 for key, value := range data { 46 shard := m.GetShard(key) 47 shard.Lock() 48 shard.items[key] = value 49 shard.Unlock() 50 } 51 } 52 53 // Set the given value under the specified key. 54 func (m CMap) Set(key string, value interface{}) { 55 // GetHeader map shard. 56 shard := m.GetShard(key) 57 shard.Lock() 58 shard.items[key] = value 59 shard.Unlock() 60 } 61 62 // CMapUpCb Callback to return new element to be inserted into the map 63 // It is called while lock is held, therefore it MUST NOT 64 // try to access other keys in same map, as it can lead to deadlock since 65 // Go sync.RWLock is not reentrant. 66 type CMapUpCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{} 67 68 // Up Insert or Update - updates existing element or inserts a new one using CMapUpCb 69 func (m CMap) Up(key string, value interface{}, cb CMapUpCb) (res interface{}) { 70 shard := m.GetShard(key) 71 shard.Lock() 72 v, ok := shard.items[key] 73 res = cb(ok, v, value) 74 shard.items[key] = res 75 shard.Unlock() 76 return res 77 } 78 79 // SetIfAbsent Sets the given value under the specified key if no value was associated with it. 80 func (m CMap) SetIfAbsent(key string, value interface{}) bool { 81 // GetHeader map shard. 82 shard := m.GetShard(key) 83 shard.Lock() 84 _, ok := shard.items[key] 85 if !ok { 86 shard.items[key] = value 87 } 88 shard.Unlock() 89 return !ok 90 } 91 92 // Get retrieves an element from map under given key. 93 func (m CMap) Get(key string) (interface{}, bool) { 94 // GetHeader shard 95 shard := m.GetShard(key) 96 shard.RLock() 97 // GetHeader item from shard. 98 val, ok := shard.items[key] 99 shard.RUnlock() 100 return val, ok 101 } 102 103 // Count returns the number of elements within the map. 104 func (m CMap) Count() int { 105 count := 0 106 for i := 0; i < CMapShardCount; i++ { 107 shard := m[i] 108 shard.RLock() 109 count += len(shard.items) 110 shard.RUnlock() 111 } 112 return count 113 } 114 115 // Has Looks up an item under specified key 116 func (m CMap) Has(key string) bool { 117 // GetHeader shard 118 shard := m.GetShard(key) 119 shard.RLock() 120 // See if element is within shard. 121 _, ok := shard.items[key] 122 shard.RUnlock() 123 return ok 124 } 125 126 // Remove removes an element from the map. 127 func (m CMap) Remove(key string) { 128 // Try to get shard. 129 shard := m.GetShard(key) 130 shard.Lock() 131 delete(shard.items, key) 132 shard.Unlock() 133 } 134 135 // RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held 136 // If returns true, the element will be removed from the map 137 type RemoveCb func(key string, v interface{}, exists bool) bool 138 139 // RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params 140 // If callback returns true and element exists, it will remove it from the map 141 // Returns the value returned by the callback (even if element was not present in the map) 142 func (m CMap) RemoveCb(key string, cb RemoveCb) bool { 143 // Try to get shard. 144 shard := m.GetShard(key) 145 shard.Lock() 146 v, ok := shard.items[key] 147 remove := cb(key, v, ok) 148 if remove && ok { 149 delete(shard.items, key) 150 } 151 shard.Unlock() 152 return remove 153 } 154 155 // Pop removes an element from the map and returns it 156 func (m CMap) Pop(key string) (v interface{}, exists bool) { 157 // Try to get shard. 158 shard := m.GetShard(key) 159 shard.Lock() 160 v, exists = shard.items[key] 161 delete(shard.items, key) 162 shard.Unlock() 163 return v, exists 164 } 165 166 // IsEmpty checks if map is empty. 167 func (m CMap) IsEmpty() bool { 168 return m.Count() == 0 169 } 170 171 // Tuple Used by the Iter & IterBuffered functions to wrap two variables together over a channel 172 type Tuple struct { 173 Key string 174 Val interface{} 175 } 176 177 // Iter returns an iterator which could be used in a for range loop. 178 // 179 // Deprecated: using IterBuffered() will get a better performence 180 func (m CMap) Iter() <-chan Tuple { 181 chans := snapshot(m) 182 ch := make(chan Tuple) 183 go fanIn(chans, ch) 184 return ch 185 } 186 187 // IterBuffered returns a buffered iterator which could be used in a for range loop. 188 func (m CMap) IterBuffered() <-chan Tuple { 189 chans := snapshot(m) 190 total := 0 191 for _, c := range chans { 192 total += cap(c) 193 } 194 ch := make(chan Tuple, total) 195 go fanIn(chans, ch) 196 return ch 197 } 198 199 // Returns a array of channels that contains elements in each shard, 200 // which likely takes a snapshot of `m`. 201 // It returns once the size of each buffered channel is determined, 202 // before all the channels are populated using goroutines. 203 func snapshot(m CMap) (chans []chan Tuple) { 204 chans = make([]chan Tuple, CMapShardCount) 205 wg := sync.WaitGroup{} 206 wg.Add(CMapShardCount) 207 // Foreach shard. 208 for index, shard := range m { 209 go func(index int, shard *CMapShared) { 210 // Foreach key, value pair. 211 shard.RLock() 212 chans[index] = make(chan Tuple, len(shard.items)) 213 wg.Done() 214 for key, val := range shard.items { 215 chans[index] <- Tuple{key, val} 216 } 217 shard.RUnlock() 218 close(chans[index]) 219 }(index, shard) 220 } 221 wg.Wait() 222 return chans 223 } 224 225 // fanIn reads elements from channels `chans` into channel `out` 226 func fanIn(chans []chan Tuple, out chan Tuple) { 227 wg := sync.WaitGroup{} 228 wg.Add(len(chans)) 229 for _, ch := range chans { 230 go func(ch chan Tuple) { 231 for t := range ch { 232 out <- t 233 } 234 wg.Done() 235 }(ch) 236 } 237 wg.Wait() 238 close(out) 239 } 240 241 // Items returns all items as map[string]interface{} 242 func (m CMap) Items() map[string]interface{} { 243 tmp := make(map[string]interface{}) 244 245 // Insert items to temporary map. 246 for item := range m.IterBuffered() { 247 tmp[item.Key] = item.Val 248 } 249 250 return tmp 251 } 252 253 // IterCb Iterator callback,called for every key,value found in 254 // maps. RLock is held for all calls for a given shard 255 // therefore callback sess consistent view of a shard, 256 // but not across the shards 257 type IterCb func(key string, v interface{}) 258 259 // IterCb Callback based iterator, cheapest way to read 260 // all elements in a map. 261 func (m CMap) IterCb(fn IterCb) { 262 for idx := range m { 263 shard := (m)[idx] 264 shard.RLock() 265 for key, value := range shard.items { 266 fn(key, value) 267 } 268 shard.RUnlock() 269 } 270 } 271 272 // Keys returns all keys as []string 273 func (m CMap) Keys() []string { 274 count := m.Count() 275 ch := make(chan string, count) 276 go func() { 277 // Foreach shard. 278 wg := sync.WaitGroup{} 279 wg.Add(CMapShardCount) 280 for _, shard := range m { 281 go func(shard *CMapShared) { 282 // Foreach key, value pair. 283 shard.RLock() 284 for key := range shard.items { 285 ch <- key 286 } 287 shard.RUnlock() 288 wg.Done() 289 }(shard) 290 } 291 wg.Wait() 292 close(ch) 293 }() 294 295 // Generate keys 296 keys := make([]string, 0, count) 297 for k := range ch { 298 keys = append(keys, k) 299 } 300 return keys 301 } 302 303 // JSON Reviles CMap "private" variables to json marshal. 304 func (m CMap) JSON() ([]byte, error) { 305 tmp := make(map[string]interface{}) 306 307 // Insert items to temporary map. 308 for item := range m.IterBuffered() { 309 tmp[item.Key] = item.Val 310 } 311 return EncodeJson(tmp) 312 } 313 314 func fnv32(key string) uint32 { 315 hash := uint32(2166136261) 316 const prime32 = uint32(16777619) 317 for i := 0; i < len(key); i++ { 318 hash *= prime32 319 hash ^= uint32(key[i]) 320 } 321 return hash 322 }