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  }