improve package gcache

This commit is contained in:
John
2020-05-06 21:09:02 +08:00
parent 767afa3106
commit 61f4e0da6f
2 changed files with 131 additions and 83 deletions

View File

@ -13,20 +13,20 @@ import "time"
var cache = New()
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
// It does not expire if <duration> <= 0.
// It does not expire if <duration> == 0.
func Set(key interface{}, value interface{}, duration time.Duration) {
cache.Set(key, value, duration)
}
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>. It does not expire if <duration> <= 0.
// which is expired after <duration>. It does not expire if <duration> == 0.
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
return cache.SetIfNotExist(key, value, duration)
}
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
//
// It does not expire if <duration> <= 0.
// It does not expire if <duration> == 0.
func Sets(data map[interface{}]interface{}, duration time.Duration) {
cache.Sets(data, duration)
}
@ -41,21 +41,21 @@ func Get(key interface{}) interface{} {
// or sets <key>-<value> pair and returns <value> if <key> does not exist in the cache.
// The key-value pair expires after <duration>.
//
// It does not expire if <duration> <= 0.
// It does not expire if <duration> == 0.
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
return cache.GetOrSet(key, value, duration)
}
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
// after <duration>. It does not expire if <duration> == 0.
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
return cache.GetOrSetFunc(key, f, duration)
}
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
// after <duration>. It does not expire if <duration> == 0.
//
// Note that the function <f> is executed within writing mutex lock.
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {

View File

@ -21,23 +21,43 @@ import (
// Internal cache object.
type memCache struct {
dataMu sync.RWMutex
expireTimeMu sync.RWMutex
expireSetMu sync.RWMutex
// dataMu ensures the concurrent safety of underlying data map.
dataMu sync.RWMutex
// <cap> limits the size of the cache pool.
// If the size of the cache exceeds the <cap>,
// expireTimeMu ensures the concurrent safety of expireTimes map.
expireTimeMu sync.RWMutex
// expireSetMu ensures the concurrent safety of expireSets map.
expireSetMu sync.RWMutex
// cap limits the size of the cache pool.
// If the size of the cache exceeds the cap,
// the cache expiration process performs according to the LRU algorithm.
// It is 0 in default which means no limits.
cap int
data map[interface{}]memCacheItem // Underlying cache data which is stored in a hash table.
expireTimes map[interface{}]int64 // Expiring key mapping to its timestamp, which is used for quick indexing and deleting.
expireSets map[int64]*gset.Set // Expiring timestamp mapping to its key set, which is used for quick indexing and deleting.
cap int
lru *memCacheLru // LRU manager, which is enabled when <cap> > 0.
lruGetList *glist.List // LRU history according with Get function.
eventList *glist.List // Asynchronous event list for internal data synchronization.
closed *gtype.Bool // Is this cache closed or not.
// data is the underlying cache data which is stored in a hash table.
data map[interface{}]memCacheItem
// expireTimes is the expiring key to its timestamp mapping,
// which is used for quick indexing and deleting.
expireTimes map[interface{}]int64
// expireSets is the expiring timestamp to its key set mapping,
// which is used for quick indexing and deleting.
expireSets map[int64]*gset.Set
// lru is the LRU manager, which is enabled when attribute cap > 0.
lru *memCacheLru
// lruGetList is the LRU history according with Get function.
lruGetList *glist.List
// eventList is the asynchronous event list for internal data synchronization.
eventList *glist.List
// closed controls the cache closed or not.
closed *gtype.Bool
}
// Internal cache item.
@ -53,7 +73,7 @@ type memCacheEvent struct {
}
const (
// Default expire time for no expiring items.
// gDEFAULT_MAX_EXPIRE is the default expire time for no expiring items.
// It equals to math.MaxInt64/1000000.
gDEFAULT_MAX_EXPIRE = 9223372036854
)
@ -75,6 +95,59 @@ func newMemCache(lruCap ...int) *memCache {
return c
}
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
//
// It does not expire if <duration> == 0.
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
expireTime := c.getInternalExpire(duration)
c.dataMu.Lock()
c.data[key] = memCacheItem{
v: value,
e: expireTime,
}
c.dataMu.Unlock()
c.eventList.PushBack(&memCacheEvent{
k: key,
e: expireTime,
})
}
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
// cache, which is expired after <duration>.
//
// It does not expire if <duration> == 0.
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
// result is nil.
//
// It doubly checks the <key> whether exists in the cache using mutex writing lock
// before setting it to the cache.
func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) interface{} {
expireTimestamp := c.getInternalExpire(duration)
c.dataMu.Lock()
defer c.dataMu.Unlock()
if v, ok := c.data[key]; ok && !v.IsExpired() {
return v.v
}
if f, ok := value.(func() interface{}); ok {
value = f()
if value == nil {
return nil
}
}
c.data[key] = memCacheItem{v: value, e: expireTimestamp}
c.eventList.PushBack(&memCacheEvent{k: key, e: expireTimestamp})
return value
}
// getInternalExpire converts and returns the expire time with given expired duration in milliseconds.
func (c *memCache) getInternalExpire(duration time.Duration) int64 {
if duration == 0 {
return gDEFAULT_MAX_EXPIRE
} else {
return gtime.TimestampMilli() + duration.Nanoseconds()/1000000
}
}
// makeExpireKey groups the <expire> in milliseconds to its according seconds.
func (c *memCache) makeExpireKey(expire int64) int64 {
return int64(math.Ceil(float64(expire/1000)+1) * 1000)
@ -104,53 +177,8 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
return
}
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
//
// It does not expire if <duration> <= 0.
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
expireTime := c.getInternalExpire(duration.Nanoseconds() / 1000000)
c.dataMu.Lock()
c.data[key] = memCacheItem{v: value, e: expireTime}
c.dataMu.Unlock()
c.eventList.PushBack(&memCacheEvent{k: key, e: expireTime})
}
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>.
//
// It does not expire if <duration> <= 0.
//
// It doubly checks the <key> whether exists in the cache using mutex writing lock
// before setting it to the cache.
func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) interface{} {
expireTimestamp := c.getInternalExpire(duration.Nanoseconds() / 1000000)
c.dataMu.Lock()
defer c.dataMu.Unlock()
if v, ok := c.data[key]; ok && !v.IsExpired() {
return v.v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
if value == nil {
return nil
}
c.data[key] = memCacheItem{v: value, e: expireTimestamp}
c.eventList.PushBack(&memCacheEvent{k: key, e: expireTimestamp})
return value
}
// getInternalExpire returns the expire time with given expire duration in milliseconds.
func (c *memCache) getInternalExpire(expire int64) int64 {
if expire <= 0 {
return gDEFAULT_MAX_EXPIRE
} else {
return gtime.TimestampMilli() + expire
}
}
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
// which is expired after <duration>. It does not expire if <duration> <= 0.
// which is expired after <duration>. It does not expire if <duration> == 0.
func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
if !c.Contains(key) {
c.doSetWithLockCheck(key, value, duration)
@ -161,14 +189,20 @@ func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration ti
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
//
// It does not expire if <duration> <= 0.
// It does not expire if <duration> == 0.
func (c *memCache) Sets(data map[interface{}]interface{}, duration time.Duration) {
expireTime := c.getInternalExpire(duration.Nanoseconds() / 1000000)
expireTime := c.getInternalExpire(duration)
for k, v := range data {
c.dataMu.Lock()
c.data[k] = memCacheItem{v: v, e: expireTime}
c.data[k] = memCacheItem{
v: v,
e: expireTime,
}
c.dataMu.Unlock()
c.eventList.PushBack(&memCacheEvent{k: k, e: expireTime})
c.eventList.PushBack(&memCacheEvent{
k: k,
e: expireTime,
})
}
}
@ -190,7 +224,7 @@ func (c *memCache) Get(key interface{}) interface{} {
// GetOrSet returns the value of <key>, or sets <key>-<value> pair and returns <value> if <key>
// does not exist in the cache. The key-value pair expires after <duration>. It does not expire
// if <duration> <= 0.
// if <duration> == 0.
func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
if v := c.Get(key); v == nil {
return c.doSetWithLockCheck(key, value, duration)
@ -201,7 +235,10 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Du
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
// after <duration>.
//
// It does not expire if <duration> == 0.
// It does nothing if function <f> returns nil.
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
if v := c.Get(key); v == nil {
return c.doSetWithLockCheck(key, f(), duration)
@ -212,7 +249,10 @@ func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
// and returns its result if <key> does not exist in the cache. The key-value pair expires
// after <duration>. It does not expire if <duration> <= 0.
// after <duration>.
//
// It does not expire if <duration> == 0.
// It does nothing if function <f> returns nil.
//
// Note that the function <f> is executed within writing mutex lock.
func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
@ -238,7 +278,10 @@ func (c *memCache) Remove(key interface{}) (value interface{}) {
c.dataMu.Lock()
delete(c.data, key)
c.dataMu.Unlock()
c.eventList.PushBack(&memCacheEvent{k: key, e: gtime.TimestampMilli() - 1000})
c.eventList.PushBack(&memCacheEvent{
k: key,
e: gtime.TimestampMilli() - 1000,
})
}
return
}
@ -310,18 +353,20 @@ func (c *memCache) Close() {
c.closed.Set(true)
}
// Asynchronous task loop:
// 1. asynchronously process the data in the event list,
// syncEventAndClearExpired does the asynchronous task loop:
// 1. Asynchronously process the data in the event list,
// and synchronize the results to the <expireTimes> and <expireSets> properties.
// 2. clean up the expired key-value pair data.
// 2. Clean up the expired key-value pair data.
func (c *memCache) syncEventAndClearExpired() {
event := (*memCacheEvent)(nil)
oldExpireTime := int64(0)
newExpireTime := int64(0)
if c.closed.Val() {
gtimer.Exit()
return
}
var (
event *memCacheEvent
oldExpireTime int64
newExpireTime int64
)
// ========================
// Data Synchronization.
// ========================
@ -365,10 +410,13 @@ func (c *memCache) syncEventAndClearExpired() {
// ========================
// Data Cleaning up.
// ========================
ek := c.makeExpireKey(gtime.TimestampMilli())
eks := []int64{ek - 1000, ek - 2000, ek - 3000, ek - 4000, ek - 5000}
var (
expireSet *gset.Set
ek = c.makeExpireKey(gtime.TimestampMilli())
eks = []int64{ek - 1000, ek - 2000, ek - 3000, ek - 4000, ek - 5000}
)
for _, expireTime := range eks {
if expireSet := c.getExpireSet(expireTime); expireSet != nil {
if expireSet = c.getExpireSet(expireTime); expireSet != nil {
// Iterating the set to delete all keys in it.
expireSet.Iterator(func(key interface{}) bool {
c.clearByKey(key)