Skip to content

Instantly share code, notes, and snippets.

@phuslu
Last active March 25, 2024 01:19
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phuslu/1423aee3127b85ed479c1431551f3c5c to your computer and use it in GitHub Desktop.
Save phuslu/1423aee3127b85ed479c1431551f3c5c to your computer and use it in GitHub Desktop.
双缓冲实现无锁map,读取性能和GC友好度最好,一致性较差,写入延迟一般建议设置为分钟级。 https://twitter.com/phuslu/status/1745276311235395708
type CachingMap[K comparable, V any] struct {
// double buffering mechanism
index int64
maps [2]map[K]V
// write queue
queue chan struct {
key K
value V
}
getter func(K) (V, error)
maxsize int
duration time.Duration
}
func NewCachingMap[K comparable, V any](getter func(K) (V, error), maxsize int, duration time.Duration) *CachingMap[K, V] {
cm := &CachingMap[K, V]{
index: 0,
maps: [2]map[K]V{
make(map[K]V),
make(map[K]V),
},
queue: make(chan struct {
key K
value V
}, 1024),
getter: getter,
maxsize: maxsize,
duration: duration,
}
go func(cm *CachingMap[K, V]) {
duration := cm.duration
if duration == 0 {
duration = time.Minute
}
ticker := time.NewTicker(duration)
for {
select {
case kv := <-cm.queue:
cm.maps[(atomic.LoadInt64(&cm.index)+1)%2][kv.key] = kv.value
case <-ticker.C:
atomic.StoreInt64(&cm.index, (atomic.LoadInt64(&cm.index)+1)%2)
if m := cm.maps[(atomic.LoadInt64(&cm.index)+1)%2]; maxsize <= 0 || len(m) <= maxsize {
for key, value := range cm.maps[atomic.LoadInt64(&cm.index)] {
m[key] = value
}
} else {
cm.maps[(atomic.LoadInt64(&cm.index)+1)%2] = make(map[K]V)
}
}
}
}(cm)
return cm
}
func (cm *CachingMap[K, V]) Get(key K) (value V, ok bool, err error) {
// fast path, lock-free
value, ok = cm.maps[atomic.LoadInt64(&cm.index)][key]
if ok {
return
}
// slow path
value, err = cm.getter(key)
if err == nil {
cm.queue <- struct {
key K
value V
}{key, value}
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment