Как стать автором
Обновить

Комментарии 30

sync.RWMutex? Я для таких вещей использую sync.Map

Да, действительно можно использовать sync.Map, но для небольших проектов разницы скорее всего вы не почувствуете.
sync.Map удобный, но не имеет метода возврата количества хранящихся в нём сущностей. Можно конечно их учитывать снаружи, но… Ну и довольно медленный он, по крайней мере по сравнению с мьютексами (не всем конечно нужно считать наносекунды, но всё же учитывать некоторый оверхед нужно).
Ну и довольно медленный он, по крайней мере по сравнению с мьютексами

Сам бенчмарков не делал, столь категорично утверждать не буду, но как универсальное решение тоже бы не посоветовал.
Документация описывает 2 конкретных случаях, когда sync.Map выгоднее мапки с мьютексом, но в общем случае для реализации кеша может сыграть как в плюс так и в минус.
func (c *Cache) StartGC() error {
go c.GC()
return nil
}

error и return nil можно смело убрать.
Спасибо за сообщение! Поправил.

Для чего в Get bool? Ведь если элемент не найден, то мы и так nil возвращаем, по нему и понятно

Как вариант, по ключу может храниться nil.

здесь один метод вообще лишний, оставить только GC и стартовать его в функции New

еще 5 копеек
— не стоит светить все функции наружу, тот же GC, есть интерфейс Get/Set/Delete остальные функции не должны быть доступны снаружи
— defer не бесплатен, когда функции делает много под блокировкой, это оправдано, для чтения/записи в map это избыточно
— не надо держать блокировку без необходимости, получил блокировку, прочитал/записал в мапку, отпустил блокировку и дальше уже разбираешься с тем что получил (это про Get)
— зачем delete возврат ошибки? она бесполезна + ради этого двойной поиск в мапке
— сборка мусора вообще странно сделана, зачем в два этапа? нет гарантии что между expiredKeys и clearItems значения не будут обновлены
по поводу defer
i5-4670 CPU @ 3.40GHz
goos: linux
goarch: amd64
BenchmarkLockUnlock-4 100000000 15.0 ns/op
BenchmarkLockDeferUnlock-4 30000000 44.9 ns/op
PASS
А зачем в структуре Item Duration? Мы при добавлении нового элемента вычисляем когда он протухнет и в дальнейшем время жизни никак не используем, зачем тогда храним? Еще вопрос: если время жизни по-умолчанию будет 10 и при добавлении нового элемента я захочу, чтоб он не протухал и установлю duration 0, правильно ли я понимаю, что желаемого я не получу? (в go просто новичок)
А зачем в структуре Item Duration? Мы при добавлении нового элемента вычисляем когда он протухнет и в дальнейшем время жизни никак не используем, зачем тогда храним?

Duration необходим для вычисления значения expiration, которое используется в методах Get и GC


Еще вопрос: если время жизни по-умолчанию будет 10 и при добавлении нового элемента я захочу, чтоб он не протухал и установлю duration 0, правильно ли я понимаю, что желаемого я не получу? (в go просто новичок)

Прошу прощение, не уточнил этот момент в статье: что бы кеш не протухал необходимо установить значение duration равное -1, в этом случае expiration будет равен 0.

Как раз в статье есть упоминание: «defaultExpiration — время жизни кеша по-умолчанию, если установлено значение меньше или равно 0 — время жизни кеша бессрочно.»
А насчет duration: он нужен будучи параметром метода Set, но не параметром item, который нигде не используется
Duration необходим для вычисления значения expiration, которое используется в методах Get и GC
Хоть убейте, не могу найти строчку, где вы считываете это поле (не путайте его с аргументом функции duration).
Да, согласен, в примере он действительно не нужен. Исправил. Спасибо.
Год назад писал аналогичный memcache сервер + клиент к нему.
C типами данных: Strings/Lists of Strings/Dictionaries of Strings.
И таким же TTL. :)

github.com/genesem/kvdb/blob/master/docs_russian.md
Я думаю для столь тривиальной реализации можно было добавить механизм снепшотов на диск.
А зачем в исходниках так много пустых строк? Например
// StartGC start Garbage Collection
func (c *Cache) StartGC() {

	go c.GC()

}

Смотрели имеющиеся реализации встраиваемых кешей, например, groupcache?
Интересно бы узнать, почему не подошло, какие дополнительные фичи потребовались.

На проде в последнем проекте использовал go-cache, по функционалу все устраивает. Данный материал исключительно для академических целей, старался максимально не усложнять.

Я видел дисклеймер про учебные цели, и горячо это одобряю )
Были интересны другие реализации и практический отзыв по работе с ними.

Для GC лучше использовать очередь с приоритетом

Кэш без контроля размера это просто еще один способ организовать утечку памяти.

Баловался этой темой. Если нужна скорость, то надо меньше локов и отказываться от interface. Поэтому приходится прощаться с map, sync.map.
В итоге решение было с генератором кэша под заданную структуру. https://github.com/JekaMas/nicecache
На чтение и запись в несколько горутин получалось выйти в 60-80нс.

В такой реализации будет катастрофа на большом количестве элементов. Под map будет выделяться и удерживаться избыточная память, даже когда все элементы протухнут. GC (тот, который родной гошный) будет каждый раз ходить по всем элементам. Чтобы избежать таких проблем используется sync.Map, и для него достаточно написать один time.Ticker.

согласен, кроме последнего предложения
sync.Map не решит проблему с gc, т.к. хранящиеся в мапке указатели никуда не денутся
здесь только один путь — уменьшать кол-во указателей, как, например, сделали вот эти ребята: allegro.tech/2016/03/writing-fast-cache-service-in-go.html

а вот и нет! смотрим 96 строку sync/map.go: entry{p: unsafe.Pointer(&i)


поэтому GC и не нагружается из-за хранения unsafe-ссылок на значения в sync.Map

В этом есть какая-то магия? Просто не работал с пакетом unsafe.
Есть какие-нибудь тесты, которые подтверждают облегчение жизни gc при использовании sync.Map?

В коде вижу вот это:
return &entry{p: unsafe.Pointer(&i)}
создается ссылка на entry и далее она хранится в
map[interface{}]*entry
Сокращения кол-ва ссылок не вижу, плюс объект, ссылка на который сохранена в map, продолжает жить в куче. В чем профит.
Да, ты прав, unsafe.Pointer обычный указатель для GC
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории