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