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