Потокобезопасный кеш в памяти с временем жизни и автоочисткой

Кэш можно реализовать на 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 не знает.

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

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

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

Подробнее