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  }