У меня есть кэш (который используется веб-приложением), который использует два кэша - один кратковременный кеш, который используется только в запросе, и долгосрочный кеш, который используется «постоянно», (через запросы).Стоимость блокировки
У меня есть следующий код, обратите внимание, что все базовые структуры данных являются потокобезопасными.
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
TCache cacheItem;
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
DateTime cacheDependancyLastModified;
if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
&& IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
{
cacheItem.CacheTime = cacheDependancyLastModified;
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
cacheItem = cacheItemCreatorFunc(cachdeDependancy);
longTermCache.Add(cachdeDependancy.Id, cacheItem);
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
Очевидно, что это еще возможно (возможно, даже вероятно), что приведенный выше код не будет соответствовать при выполнении параллельных (то есть несколько веб-запросов). Однако я написал несколько модульных тестов, и я увидел, что никогда не возникает «исключение». Может случиться так, что тот же элемент добавляется снова, даже если он уже существует и т. Д. -> Я думаю, вы можете видеть, что я имею в виду, когда вы смотрите на код.
Тем не менее я думал, что было бы неплохо иметь решение, которое всегда работает правильно и согласовано.
Так что я переписал этот код, используя простой двойной механизм проверки блокировки (может быть, это может быть даже лучше, добавив еще один/второй замок для другого кэша?):
public TCache Get(CacheDependency cachdeDependancy, Func<CacheDependency, TCache> cacheItemCreatorFunc)
{
TCache cacheItem;
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
lock (_lockObj)
{
if (shortTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem))
{
return cacheItem;
}
DateTime cacheDependancyLastModified;
if (longTermCache.TryGetValue(cachdeDependancy.Id, out cacheItem)
&& IsValid(cachdeDependancy, cacheItem, out cacheDependancyLastModified))
{
cacheItem.CacheTime = cacheDependancyLastModified;
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
cacheItem = cacheItemCreatorFunc(cachdeDependancy);
longTermCache.Add(cachdeDependancy.Id, cacheItem);
shortTermCache[cachdeDependancy.Id] = cacheItem;
return cacheItem;
}
}
Я думаю, что этот код прямо сейчас правильно работает в многопоточных средах.
Однако я не уверен: Будет ли это не слишком медленным и, следовательно, также разрушает цель кеша? Может быть, было бы лучше жить с проблемой, что кеш может иногда иметь «непоследовательное» поведение? Потому что, если есть 1000 веб-запросов одновременно, все должны подождать, пока они не смогут войти в зону блокировки. Или это совсем не проблема, потому что процессор имеет только определенное количество ядер (и, следовательно, «настоящих» параллельных потоков), и это ограничение производительности всегда будет незначительным?
Это много зависит от 2 думает: сколько потоков cuncurrent обращается к вашему коду и сколько времени требуется для запуска кода в инструкции блокировки. До тех пор, пока он получает доступ только к памяти (нет db, никакого диска) и методы, которые вы вызываете, бывают быстрыми, это не должно быть реальной проблемой. (если ваш 1000-й запрос должен ждать миллисекунду или около того, это не будет узким местом) Но опять-таки, это зависит от реального сценария. –
Вы можете использовать [Закон Амдаля] (https://de.wikipedia.org/wiki/Amdahlsches_Gesetz), чтобы рассчитать улучшение путем распараллеливания. – Markus
Одновременно может создаваться только один элемент кэша, который кажется убийцей. – usr