#c# #multithreading #asp.net-core #.net-core
Вопрос:
У меня есть свойство, определенное как:
private static MyStaticCache? _myStaticCache;
MyStaticCache
является классом со строковым свойством. Доступ могут получить несколько потоков _myStaticCache
. Если свойство равно null или значение старое, любой поток, обращающийся к нему, получит значение из источника и установит _myStaticCache
это значение.
public string GetValue()
{
if (_myStaticCache == null || _myStaticCache.CacheIsStale())
_myStaticCache = GetValueFromSource();
return _myStaticCache.Value1;
}
Ни один код не может и никогда _myStaticCache
не будет возвращать значение null.
Можно быстро увидеть, что возможно несколько вызовов GetValueFromSource()
и назначений _myStaticCache
, если несколько потоков выполняются до того, как они будут назначены в первый раз или когда они устарели.
Мой вопрос таков. Есть ли способ, чтобы это привело к сбою? Является ли присвоение _myStaticCache
атомарным, или может быть чтение в середине записи?
Тот факт, что метод источника может быть вызван N раз параллельно, на самом деле не имеет значения. Тайм-аут для кэша составляет 30 дней, и очень маловероятно, что в это точное время будет запущено несколько потоков, хотя это и не невозможно, и даже если параллельно будет запущено 100 потоков, вызывающих его, метод вернет одно и то же значение для каждого из них и сможет без проблем справиться с нагрузкой.
Теперь я мог бы использовать мьютекс или обернуть чтение и запись, lock()
но я думаю, что это снизит производительность в 99,999% случаев, когда вызывается этот метод, опять же, он будет пустым или устаревшим каждые 30 дней.
Комментарии:
1. Одним словом: нет.
2. @user207421 любой односложный ответ на эту тему, как правило, не стоит потраченного впустую одного слова
3. Может
GetValueFromSource
вернутьсяnull
?4. если
value
это коллекция, она рухнет. существуют десятки ситуаций, когда вещи ведут себя не так, как «ожидалось», когда дело доходит до тяжелой многопоточности. и даже если он не разобьется — вы получите очень сложные условия гонки, когдаvalue
вернетесь не таким, как вы ожидали. О, просто прочитайте это еще раз — вы не можете поделиться строкой без синхронизации. Период.
Ответ №1:
Пока MyStaticCache
это class
(или интерфейс; в основном: ссылочный тип), с вами все должно быть в порядке.
Спецификация языка гарантирует, что вы никогда не получите разорванные ссылки, так что нет, это никогда не должно привести к сбою:
12.5 Атомарность ссылок на переменные
Операции чтения и записи следующих типов данных должны быть атомарными:
bool
,char
,byte
,sbyte
,short
,ushort
,uint
,int
,float
, и ссылочные типы. Кроме того, чтение и запись типов перечислений с базовым типом в предыдущем списке также должны быть атомарными. Операции чтения и записи других типов, включаяlong
,ulong
,double
, иdecimal
, а также определяемые пользователем типы, не обязательно должны быть атомарными. Помимо библиотечных функций, предназначенных для этой цели, нет никакой гарантии атомарного чтения-изменения-записи, например, в случае увеличения или уменьшения.
Так что до тех пор, пока вас не беспокоит многократное выполнение медленного пути для начального / устаревшего значения или проблемы, связанные с использованием регистра и т. Д. : С вами все должно быть в порядке.
Лично я бы, вероятно CacheIsStale()
, проверил таймер (с частотой по вашему выбору), который устанавливает поле в null
положение, когда оно обнаруживает устаревание, а не постоянно проверяет два условия. Вы даже можете выполнить обновление внутри обратного вызова таймера и вместо этого установить статическое поле в новую ссылку, так что:
public string GetValue()
=> (_myStaticCache ?? FetchAndAssign()).Value1;
private MyStaticCache FetchAndAssign()
=> _myStaticCache = GetValueFromSource();
private void TimerCallback()
{
if (_myStaticCache is null || _myStaticCache.CacheIsStale())
FetchAndAssign();
}
Обратите внимание, что мы можем комментировать только _myStaticCache
то, что .Value1
зависит от вашего кода, и может быть или не быть потокобезопасным.
Ответ №2:
Вам действительно нужно использовать синхронизацию (я просто не верю, что у вас есть основания не использовать ее, судя по вопросу). Даже int и другие типы значений часто требуют некоторой синхронизации (например, барьер памяти), несмотря на то, что их рекламируют как «атомарные».
Что бы я сделал в вашем случае, так это просто обернул весь доступ к вашему общему объекту ReaderWriterLockSlim
(а также предложил использовать одноэлементный вместо статического).
https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim?view=net-5.0
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
public MyValueType ReadValue()
{
cacheLock.EnterReadLock();
try
{
return myCache.Value1;
}
finally
{
cacheLock.ExitReadLock();
}
}
public void WriteValue(MyValueType value)
{
cacheLock.EnterWriteLock();
try
{
myCache.Value1 = value;
}
finally
{
cacheLock.ExitWriteLock();
}
}