Pull to refresh

Comments 12

А почему вы использовали Dictionary c навешенной на него блокировкой вместо стандартного ConcurrentDictionary?

PS и откуда такое ограничение TValue: class? Структуры кэшировать вроде как не надо?
Нет смысла использовать ConcurrentDictionary, т.к. мне всё равно нужно блокировать всю коллекцию для очищения мёртвых ссылок.
WeakReference принимает только class и на практике мне не нужно кэшировать структуры. Что int кэшировать что ли? Свои структуры я использую крайне редко, только когда это продиктовано ограничениями по производительности (нужно часто и по многу создавать экземпляры).
WeakReference нельзя использовать для кэша. По сути, уходить сущности из вашего кэша будут не по мере заполнения памяти, а в случайные моменты времени.
Посмотрите в сторону System.Runtime.Caching

msdn.microsoft.com/ru-ru/library/ms404247.aspx
Avoid using weak references as an automatic solution to memory management problems. Instead, develop an effective caching policy for handling your application's objects.
Давайте я подробнее опишу для чего я сделал именно такую реализацию, может появятся идеи как лучше сделать.
У меня в базе хранится список лучших статей за день (грубо говоря 100 значений типа long). Также у меня есть 100500 юзеров (ориентир именно на 100к, хотя реально поменьше сейчас). Итоговый список лучших статей для каждого юзера уникальный, то есть его надо генерить на основании общего списка и некоторых данных по юзеру. Для распределения работы я использую шину (RabbitMQ).
Загружаю из базы все ид юзеров и пачками по 100 отправляю их на шину (то есть в одном сообщении лежит 100 ид юзеров), а также ключ для списка лучших статей.
В обработчике, я поднимаю из базы список лучших статей, поднимаю данные по каждому юзеру, нахожу какие конкретно статьи нужно отправить данному юзеру и отправляю ему письмо.
Описанный выше кэш, я сделал, чтобы в рамках одного воркера не нужно было хотя бы каждый раз загружать список лучших статей. Они поднимутся один раз, при обработке первого пакета и будут висеть.
Более хорошего, чем WeakReference способа очистки я сходу не придумал, т.к. воркеры без состояния, они не могут знать были все данные отправлены или нет и не знают когда надо очистить кэш.
Второй вариант у меня был просто по времени, но показалось как-то не красиво.
… и будут висеть.

Проблема в том, что не понятно сколько они там будут висеть.
Посмотрите Алгоритмы кэширования. Скорее всего под Вашу задачу что-нибудь подойдёт. LRU или еще чего.
Вот вы начали грузить статьи, они попали в нулевое поколение, прямую ссылку на них убрали. Нагрузили таких статей 256 кб. Прошла сборка мусора, ваш кэш пуст…
Не совсем так: воркер получил порцию юзеров, для которых надо поработать. Он взял из кэша данные и держит в локальной переменной. Значит данные не умрут по он их не отпустит. Параллельный поток воркера тоже смог взять данные из кэша и так далее, пока все юзеры не обработаны. А дальше, именно как мне надо: все юзеры по данным статьям обработаны, и пусть данные умирают при первой же сборке.
Не надо писать 2 раза

if (data.TryGetValue(key, out wr))
{
    val = (TValue)wr.Target;
    if (val != null)
        return val;
}

Пока вы в UpgradeableReadLock никто ничего не запишет
Да, но любое количество читателей могут встать в очередь на EnterWriteLock и после моей первой записи войти туда и опять начать дёргать базу. А это нам не нужно.
Не очень понял о чем Вы. Дальше UpgradeableReadLock никто не пройдёт…

Only one thread can enter upgradeable mode at any given time. (Отсюда)
Кстати, это — серьезная проблема производительности. Я бы предложил такое решение:
Скрытый текст
        public TValue this[TKey key]
        {
            get
            {
                CleanCache();
                WeakReference wr;
                TValue val;
                try
                {
                    rwLock.EnterReadLock();
                    if (data.TryGetValue(key, out wr))
                    {
                        val = (TValue)wr.Target;
                        if (val != null)
                            return val;
                    }
                }
                finally
                {
                    rwLock.ExitReadLock();
                }
                try
                {
                    rwLock.EnterWriteLock();
                    if (data.TryGetValue(key, out wr))
                    {
                        val = (TValue)wr.Target;
                        if (val != null)
                            return val;
                    }
                    data[key] = new WeakReference(val = getter(key));
                    return val;
                }
                finally
                {
                    rwLock.ExitWriteLock();
                }
            }
        }

Вы правы. В данном случае я бы тоже предпочел обойтись без UpgradeableReadLock.
Sign up to leave a comment.

Articles