Создание потокобезопасного кеша с автоматическим истечением TTL для объектов User

Храните User вместе со сроком истечения под mutex. Таймер удаляет запись автоматически, а Get дополнительно проверяет TTL, чтобы не вернуть просроченное значение при задержке callback.

Короткий ответ

Что ответить на собеседовании

Храните 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 нужна отдельная политика копирования или неизменяемость. Для большого кэша добавьте ограничение памяти и пакетную очистку вместо множества таймеров.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее