【问题标题】:Singleton pattern on persistent in-memory cache持久内存缓存中的单例模式
【发布时间】:2012-01-11 15:12:10
【问题描述】:

使用我在Implementing the Singleton Pattern in 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 时,似乎表现不正常,根本不刷新,但这似乎也是间歇性的,非常完全奇怪。

我目前的怀疑是单例,我认为它做得很好,所有的单元测试都证明单例机制、刷新机制和 RAM 性能都按预期工作。

也就是说,我认为有这些可能性:

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

有什么建议吗?

.NET 3.5 所以没有太多可用的并行果汁,目前还没有准备好使用反应式扩展

Edit1:根据建议,吸气剂看起来像:

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

【问题讨论】:

  • 这里有可能你有一些线程安全问题吗?似乎您想要锁定以防止在刷新后创建存储库的多个实例(通常称为“缓存踩踏”)
  • 从您的问题来看,听起来要发布的代码将是执行刷新的代码。你上面的东西在我看来没问题。负载平衡和 IIS 应该没有任何影响(但再次需要查看数据库刷新代码),除非您指的是多个网络服务器,在这种情况下,您需要在它们之间同步刷新(这种情况下只需使用外部缓存....AppFabric 缓存将是合适的)
  • @F.Aquino:实际上,不,它没有。很可能两个线程几乎同时尝试访问Localization 属性,从而导致创建了两个存储库。使这个属性线程安全将摆脱这个问题。换句话说:创建实例后,您的单例类不是线程安全的。
  • 正如 Eric 所说,肯定可能存在一些线程问题,因为您没有锁定 _localizations 成员(以及 ?? 调用)。但是您说数据很少更新...您确定吗?
  • @F.Aquino 我应该补充一点,您应该修改您的 Singleton 以解决其他海报提到的线程问题,但我认为这不是您问题的根源。另外,您有什么理由不使用简单的静态类吗?如果您正在实现接口,或者需要从基类继承,单例可能很有用,但您似乎没有这样做,因此静态类可能同样有效。

标签: c# architecture .net-3.5 singleton


【解决方案1】:

为了扩展我的评论,您可以通过以下方式使 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

}

【讨论】:

  • Eric,谢谢,_lock 方法比锁定实例变量本身有什么好处?
  • @F.Aquino - 是的,因为您正在分配实例变量,所以您也不想锁定它,因为当您重新分配对象时,您的锁定将被无意释放。请参阅此问题的第一个答案以获得很好的解释:stackoverflow.com/questions/1287356/…
  • 谢谢你,Erik,我接受了你的建议,今天将开始 QA,我要感谢为线程做出贡献的其他所有人,惊人而快速的反馈,遗憾的是我看到的这些主要问题很多人犯错的问题并没有被问到那么多。
【解决方案2】:

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

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

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

例如,考虑这种情况:

线程 1 线程 2 // 两个线程都访问单例,但是你是“安全的”,因为你锁定了 1. var loc1 = Params.Localizations; var loc2 = 参数。本地化; // 做事 // 线程 2 调用相同的属性... 2. 变量值 = loc1.ChunkSize; var chunk = LC.Loc("params.chunksize"); // 无效 // ...这里有一个轻微的停顿... 3. loc1.RebuildLocalizations(); // ...并得到错误的值 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 之间跳转。

【讨论】:

  • 您的第二种方法是我从未见过的,非常有趣,所以构造方法不需要锁包装器?这是否也会消除实例变量 _localizations,而我只需使用自动属性设置 Localizations 属性?
  • @F.Aquino:是和不是;它仍然暗示private readonly 支持字段_localizations,以防止任何人意外更改它,因为没有锁定。离开 set 访问器不会解决任何问题。但在这种情况下,您将无法通过使其无效来“重建”它。如果这是您所追求的,请定期锁定,这是您唯一的选择。
  • @F.Aquino:我已经用其他信息更新了我的答案。顺便说一句,为什么这被称为“本地化”?它实际上与本地化有什么关系?
  • 这是一个多语言网站的实际葡萄牙语域特定名称的快速翻译,可能选择不当。
猜你喜欢
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-27
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多