Кэш можно реализовать на map с mutex, сроком истечения записи и таймером удаления. Get обязан проверять время сам: выполнение таймера может задержаться.
Потокобезопасный кеш в памяти с временем жизни и автоочисткой
Кэш можно реализовать на map с mutex, сроком истечения записи и таймером удаления. Get обязан проверять время сам: выполнение таймера может задержаться.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо создать потокобезопасный in‑memory кеш, реализующий интерфейс Cacher. Кеш должен хранить элементы с заданным временем жизни (TTL) и самостоятельно удалять просроченные записи посредством сборки мусора. Требуется поддержка следующих операций:
Set(key string, value any)— добавить или обновить значение по ключу.Get(k string) (any, bool)— получить значение, проверив, не истёк ли его TTL.
type Cacher interface {
Set(key string, value any)
Get(k string) (any, bool)
}
func NewCacher(duration time.Duration) Cacher {
}
Ответ
Кэш можно реализовать на map с mutex, сроком истечения записи и таймером удаления. Get обязан проверять время сам: выполнение таймера может задержаться.
Вариант без постоянно работающей фоновой горутины, с интерфейсом Cacher из условия:
import (
"sync"
"time"
)
type entry struct {
value any
expires time.Time
timer *time.Timer
}
type ttlCache struct {
mu sync.Mutex
ttl time.Duration
items map[string]*entry
}
func NewCacher(ttl time.Duration) Cacher {
if ttl <= 0 { panic("TTL must be positive") }
return &ttlCache{ttl: ttl, items: make(map[string]*entry)}
}
func (c *ttlCache) Set(key string, value any) {
c.mu.Lock()
defer c.mu.Unlock()
if old := c.items[key]; old != nil { old.timer.Stop() }
e := &entry{value: value, expires: time.Now().Add(c.ttl)}
c.items[key] = e
e.timer = time.AfterFunc(c.ttl, func() {
c.mu.Lock()
defer c.mu.Unlock()
if c.items[key] == e { delete(c.items, key) }
})
}
func (c *ttlCache) Get(key string) (any, bool) {
c.mu.Lock()
defer c.mu.Unlock()
e := c.items[key]
if e == nil { return nil, false }
if !time.Now().Before(e.expires) {
e.timer.Stop()
delete(c.items, key)
return nil, false
}
return e.value, true
}
Каждая запись имеет таймер; сравнение указателей не позволяет старому callback удалить новое значение того же ключа. Просроченный Get удаляет запись сразу. Здесь TTL<=0 запрещён явно. Кэш защищает свою map, но не конкурентные изменения объекта, лежащего в value. Для больших объёмов лучше общий планировщик очистки и лимит памяти; сборщик Go сам TTL не знает.