这种多线程Singleton更有效吗?

Nic*_*tch 4 c# singleton multithreading

我在多线程环境中有一个高吞吐量的Singleton.通常我会做这样的事情:

public static Foo GetInstance()
{
    lock (Foo._syncLock)
    {
        if (Foo._instance == null)
            Foo._instance = new Foo();
        return Foo._instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

我想知道如果执行以下操作会更有效率,因为它会避免连续的线程锁定,或者是否存在隐藏的问题?

public static Foo GetInstance()
{
    if (Foo._instance != null)
        return Foo._instance;
    lock (Foo._syncLock)
    {
        if (Foo._instance == null)
            Foo._instance = new Foo();
        return Foo._instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 9

我想知道如果执行以下操作会更有效率,因为它会避免连续的线程锁定,或者是否存在隐藏的问题?

你的问题是"通过采用危险的低锁模式会有性能提升吗?" 这是一个完全错误的问题.永远不要这样说!这种方式浪费时间,浪费精力,以及疯狂,不可能调试的错误.

正确的问题是"我的测量结果是否强烈表明我开始时遇到性能问题? ".

如果答案是"不",那么你就完成了.

只有当答案为"是"时,你才应该问下一个问题,即" 我可以通过消除对锁的争用来消除我的性能问题吗? "

如果答案为"是",则消除对锁的争用并返回第一个问题.

只有当答案是"否"时,你应该问下一个问题,即" 将采用低锁定解决方案给出可接受的性能吗?"

请注意,对于此问题的答案为"是",您必须处于这样的情况:非竞争锁定施加的十纳秒惩罚是您的表现的门控因素.很少有人处于10或20纳秒太长的位置.

在极不可能的事件中,答案是肯定的,你应该继续讨论下一个问题,即" 双重检查锁定的正确实现是否会消除我的性能问题? "

如果双重检查锁定不够快,那么实现它是一个非启动器.你必须以其他方式解决你的问题.

只有当问题的答案为"是"时,才应实施双重检查锁定.

现在让我们来看看你的实际问题:

它有隐藏的问题吗?

您的实施是正确的.然而,当你偏离幸福的模式时,所有的赌注都会被取消.例如:

static object sync = new object();
static bool b = false;
static int x = 0;
static int GetIt()
{
  if (!b)
  {
    lock(sync)
    {
      if (!b)
      {
        b = true;
        x = ExpensiveComputation();
      }
    }
  }
  return x;
}
Run Code Online (Sandbox Code Playgroud)

看起来是对的,对吧?但这不正确!考虑低锁路径.由于此路径上没有障碍,x的值可以在一个线程上预取为零,然后另一个线程可以运行并将b设置为true,将x设置为123,然后原始线程可以获取b,get是的,并返回预取的x.

那么解决方案是什么?根据我的偏好顺序,它们是:

  • 不要偷懒.初始化静态字段一次并完成它.
  • 使用Jon Skeet网站上记录的祝福懒惰单身模式.
  • 使用Lazy<T>.
  • 使用单一检查锁定.
  • 如果您不关心单例是否在极少数情况下创建两次而丢弃一个,请使用InterlockedCompareExchange.
  • 使用祝福的双重锁定图案.