Шаблон синглтона в постоянном кеше в памяти - PullRequest
5 голосов
/ 11 января 2012

Используя то, что я оценил, было лучшим из всех миров на Реализация шаблона Singleton в C # , я с успехом использую следующий класс для сохранения пользовательских данных в памяти (для очень редко изменяемые данные):

public class Params
{
  static readonly Params Instance = new Params();

  Params()
  {
  }

  public static Params InMemory
  {
    get
    {
      return Instance;
    }
  }

  private IEnumerable<Localization> _localizations;

  public IEnumerable<Localization> Localizations
  {
    get
    {
      return _localizations ?? (_localizations = new Repository<Localization>().Get());
    }
  }

  public int ChunkSize
  {
    get
    {
      // Loc uses the Localizations impl
      LC.Loc("params.chunksize").To<int>();
    }
  }

  public void RebuildLocalizations()
  {
    _localizations = null;
  }

  // other similar values coming from the DB and staying in-memory,
  // and their refresh methods

}

Мое использование будет выглядеть примерно так:

var allLocs = Params.InMemory.Localizations; //etc

Всякий раз, когда я обновляю базу данных, вызывается RefreshLocalizations, поэтому перестраивается только часть моего хранилища в памяти. У меня есть единичная рабочая среда из примерно 10, которая кажется неадекватной, когда вызывается RefreshLocalizations, вообще не обновляется, но это также кажется прерывистым и очень странным .

Мои нынешние подозрения относятся к одноэлементному режиму, который, я думаю, отлично работает, и все модульные тесты доказывают, что одноэлементный механизм, механизм обновления и производительность ОЗУ работают должным образом.

Тем не менее, я до этих возможностей:

  1. Этот клиент лжет, когда говорит, что в его среде не используется баланс нагрузки. Это настройка, которую я не ожидаю, что содержимое в памяти будет работать правильно (верно?)
  2. В их IIS есть нестандартная конфигурация пула, с которой я тестирую (может быть, в настройках Web Garden?)
  3. Синглтон как-то выходит из строя, но не уверен, как.

Есть предложения?

.NET 3.5 , поэтому не так много параллельного сока, доступного, и пока не готовы использовать Reactive Extensions

Edit1: в соответствии с предложениями, получатель будет выглядеть примерно так:

public IEnumerable<Localization> Localizations
{
  get
  {
    lock(_localizations) {
      return _localizations ?? (_localizations = new Repository<Localization>().Get());
    }
  }
}

Ответы [ 2 ]

2 голосов
/ 11 января 2012

Чтобы расширить мой комментарий, вот как вы можете сделать поток свойств Localizations безопасным:

public class Params
{
  private object _lock = new object();

  private IEnumerable<Localization> _localizations;    
  public IEnumerable<Localization> Localizations
  {
    get
    {
      lock (_lock) {
         if ( _localizations == null ) {
            _localizations = new Repository<Localization>().Get();
         }

         return _localizations;
      }
    }
  }

  public void RebuildLocalizations()
  {
     lock(_lock) {
        _localizations = null;
     }
  }

  // other similar values coming from the DB and staying in-memory,
  // and their refresh methods

}
2 голосов
/ 11 января 2012

Нет смысла создавать поточный синглтон, если ваши свойства не будут поточно-ориентированными.

Вы должны либо заблокировать присваивание поля _localization, либо создать экземпляр в конструкторе вашего синглтона ( предпочтительно ). Любое предложение, которое относится к экземпляру синглтона, относится к этому ленивому экземпляру свойства.

То же самое относится и ко всем свойствам (и их свойствам) Localization. Если это Singleton, это означает, что любой поток может получить к нему доступ в любое время, и простая блокировка геттера снова ничего не сделает.

Например, рассмотрим этот случай:

    Thread 1                              Thread 2

    // both threads access the singleton, but you are "safe" because you locked
1.  var loc1 = Params.Localizations;      var loc2 = Params.Localizations;

    // do stuff                           // thread 2 calls the same property...
2.  var value = loc1.ChunkSize;           var chunk = LC.Loc("params.chunksize");

    // invalidate                         // ...there is a slight pause here...
3.  loc1.RebuildLocalizations();

                                          // ...and gets the wrong value
4.                                        var value = chunk.To();

Если вы только читаете эти значения, то это может не иметь значения, но вы можете видеть, как вы легко можете столкнуться с этим подходом.

Помните, что при работе с потоками вы никогда не знаете, будет ли другой поток выполнять что-то между двумя инструкциями. Только простые 32-битные назначения являются атомарными , ничего больше.

Это означает, что в этой строке здесь:

return LC.Loc("params.chunksize").To<int>();

, что касается потоков, эквивалентно:

var loc = LC.Loc("params.chunksize");
Thread.Sleep(1); // anything can happen here :-(
return loc.To<int>();

Любая нить может перейти между Loc и To.

...