Храните User вместе со сроком истечения под mutex. Таймер удаляет запись автоматически, а Get дополнительно проверяет TTL, чтобы не вернуть просроченное значение при задержке callback.
Создание потокобезопасного кеша с автоматическим истечением TTL для объектов User
Храните User вместе со сроком истечения под mutex. Таймер удаляет запись автоматически, а Get дополнительно проверяет TTL, чтобы не вернуть просроченное значение при задержке callback.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо реализовать кеш в оперативной памяти, который: • Сохраняет структуры типа User, используя поле User.ID в качестве ключа. • Автоматически удаляет записи после заданного времени жизни (по умолчанию 3 секунды). • Предоставляет следующие операции:
- Set(user User, ttl time.Duration) – добавить элемент в кеш с указанным TTL.
- Get(id string) (User, bool) – получить элемент по идентификатору и флаг наличия.
- Delete(id string) – удалить элемент из кеша. • Обеспечивает корректную работу в конкурентной среде (потокобезопасность).
Ответ
Храните User вместе со сроком истечения под mutex. Таймер удаляет запись автоматически, а Get дополнительно проверяет TTL, чтобы не вернуть просроченное значение при задержке callback.
Вариант для существующего типа User с полем ID string; нужны sync и time. Здесь ttl<=0 означает стандартные 3 секунды:
type userEntry struct {
user User
expires time.Time
timer *time.Timer
}
type UserCache struct {
mu sync.Mutex
items map[string]*userEntry
}
func (c *UserCache) Set(user User, ttl time.Duration) {
if ttl <= 0 { ttl = 3*time.Second }
c.mu.Lock()
defer c.mu.Unlock()
if c.items == nil { c.items = make(map[string]*userEntry) }
id := user.ID
if old := c.items[id]; old != nil { old.timer.Stop() }
e := &userEntry{user: user, expires: time.Now().Add(ttl)}
c.items[id] = e
e.timer = time.AfterFunc(ttl, func() {
c.mu.Lock()
defer c.mu.Unlock()
if c.items[id] == e { delete(c.items, id) }
})
}
func (c *UserCache) Get(id string) (User, bool) {
c.mu.Lock()
defer c.mu.Unlock()
e := c.items[id]
if e == nil { return User{}, false }
if !time.Now().Before(e.expires) {
e.timer.Stop()
delete(c.items, id)
return User{}, false
}
return e.user, true
}
func (c *UserCache) Delete(id string) {
c.mu.Lock()
defer c.mu.Unlock()
if e := c.items[id]; e != nil {
e.timer.Stop()
delete(c.items, id)
}
}
Сравнение текущей записи защищает новое значение от старого таймера. User копируется поверхностно: для содержащихся в нём map/slice нужна отдельная политика копирования или неизменяемость. Для большого кэша добавьте ограничение памяти и пакетную очистку вместо множества таймеров.