【问题标题】:Using the same lock for multiple methods多个方法使用同一个锁
【发布时间】:2023-03-08 11:53:01
【问题描述】:

到目前为止,我对多种方法使用同一个锁没有任何问题,但我想知道以下代码是否真的存在我不知道的问题(性能?):

private static readonly object lockObj = new object();

public int GetValue1(int index)
{
    lock(lockObj)
    {
        // Collection 1 read and/or write
    }
}

public int GetValue2(int index)
{
    lock(lockObj)
    {
        // Collection 2 read and/or write
    }
}

public int GetValue3(int index)
{
    lock(lockObj)
    {
        // Collection 3 read and/or write
    }
}

这3个方法和集合无论如何都没有关系。

另外,如果这个lockObj也被单例使用(在Instance属性中)会不会有问题?

编辑:澄清我在单例类中使用相同锁对象的问题:

private static readonly object SyncObject = new object();

public static MySingleton Instance
{
    get
    {
        lock (SyncObject)
        {
          if (_instance == null)
          {
              _instance = new MySingleton();
          }
        }
        return _instance;
    }
}

public int MyMethod()
{
      lock (SyncObject)
      {
           // Read or write
      }  
}

这会导致问题吗?

【问题讨论】:

  • 将方法命名为好像它们只读取(“GetX”)进行写入的方法是非常糟糕的做法。特别是如果您将多个线程投入其中。
  • 这取决于“写入”的类型——有时懒惰的读取可能是写入,在这种情况下,这并不总是不好的做法。
  • 你能澄清最后一点吗? “另外,如果这个lockObj也被单例(在Instance属性中)使用,会不会有问题?”

标签: c# .net multithreading locking thread-safety


【解决方案1】:

如果方法如您所述不相关,则为每个方法使用不同的锁;否则效率低下(因为没有理由让不同的方法锁定同一个对象,因为它们可以安全地同时执行)。

此外,这些似乎是锁定在静态对象上的实例方法——这是有意的吗?我有一种感觉,这是一个错误。实例方法应该(通常)只锁定实例字段。

关于单例设计模式:

虽然锁定对这些人来说是安全的,但更好的做法是延迟初始化字段,如下所示:

private static object sharedInstance;
public static object SharedInstance
{
     get
     {
          if (sharedInstance == null)
              Interlocked.CompareExchange(ref sharedInstance, new object(), null);
          return sharedInstance;
     }
}

这样会快一点(既因为互锁方法更快,也因为初始化延迟了),但仍然是线程安全的。

【讨论】:

    【解决方案2】:

    通过在所有这些方法中对lock 使用相同的对象,您正在序列化所有线程中对代码的所有访问

    也就是说...运行GetValue1() 的代码将阻止不同线程中的其他代码运行GetValue2(),直到它完成。如果您添加更多代码来锁定同一个对象实例,那么您最终会在某个时候有效地得到一个单线程应用程序。

    【讨论】:

      【解决方案3】:

      共享锁锁定其他不相关的调用

      如果您使用相同的锁,那么在一种方法中锁定也会不必要地锁定其他方法。如果它们根本不相关,那么这是一个问题,因为它们必须彼此等待。他们不应该这样做。

      瓶颈

      当这些方法被频繁调用时,这可能会造成瓶颈。使用单独的锁,它们将独立运行,但共享同一个锁意味着它们必须根据需要更频繁地等待锁被释放(实际上是 三倍)。

      【讨论】:

        【解决方案4】:

        要创建线程安全的单例,请使用this technique
        你不需要锁。

        一般来说,每个锁都应该尽量少用。
        锁定同一事物的方法越多,您最终在不需要时等待它的可能性就越大。

        【讨论】:

          【解决方案5】:

          好问题。更细粒度的锁与更粗粒度的锁各有利弊,一个极端是为每条数据设置一个单独的锁,另一个极端是为整个程序设置一个锁。正如其他帖子指出的那样,重用相同锁的缺点通常是您可能会获得较少的并发性(尽管取决于具体情况,但您可能不会获得较少的并发性)。

          但是,使用更多锁的缺点通常是更容易发生死锁。涉及的锁越多,获得死锁的方法就越多。例如,在不同的线程中同时获取两个锁,但顺序相反,这是一个潜在的死锁,如果只涉及一个锁,就不会发生这种死锁。当然,有时您可以通过将一个锁分成两个来修复死锁,但通常更少的锁意味着更少的死锁。拥有更多锁也会增加代码复杂性。

          总的来说,这两个因素需要平衡。如果不会导致任何并发问题,为了方便起见,通常每个类使用一个锁。实际上,这样做是一种称为监视器的设计模式。

          我想说最好的做法是为了代码简单而减少锁,如果有充分的理由(例如并发,或者它更简单或修复死锁的情况),则增加锁。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-20
            • 2017-02-14
            • 1970-01-01
            • 2013-04-09
            • 1970-01-01
            • 1970-01-01
            • 2012-11-08
            • 1970-01-01
            相关资源
            最近更新 更多