持久性内存缓存中的单例模式

时间:2012-01-11 15:12:11

标签: c# architecture .net-3.5 singleton

Implementing the Singleton Pattern in C#惊人的文章中,使用我所判断的最好的世界,我一直在成功使用以下类来将用户定义的数据保存在内存中(对于极少数修改过的数据): / p>

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时行为不端,根本没有刷新,但这似乎也是间歇性的,非常奇怪的是。

我当前的怀疑是针对单身人士,我认为这项工作非常出色,并且所有单元测试证明单例机制,刷新机制和RAM性能都按预期工作。

那就是说,我很清楚这些可能性:

  1. 当客户说他们的环境没有使用负载平衡时这个客户撒谎,这是一个我不希望内存中的东西正常工作的设置(对吗?)
  2. 我正在测试的IIS中有一些非标准池配置(可能在Web Garden设置中?)
  3. 单身人士以某种方式失败,但不确定如何。
  4. 有什么建议吗?

    .NET 3.5 所以没有多少并行果汁,现在还没准备好使用Reactive Extensions

    Edit1:根据建议,getter看起来像是:

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

2 个答案:

答案 0 :(得分:2)

如果您的属性不是线程安全的,那么创建线程安全单例是没有意义的。

您应该锁定_localization字段的赋值,或者在单例的构造函数中实例化(首选)。任何适用于单例实例化的建议都适用于这个惰性实例化的属性。

同样的事情进一步适用于Localization的所有属性(及其属性)。如果这是一个Singleton,这意味着任何线程都可以随时访问它,而只是锁定getter将再次无效。

例如,考虑这种情况:

    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>();

任何主题都可以在LocTo之间跳转。

答案 1 :(得分:2)

要扩展我的评论,以下是如何使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

}