Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ajax/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 这个锁使用线程安全吗?_C#_Synchronization_Locking - Fatal编程技术网

C# 这个锁使用线程安全吗?

C# 这个锁使用线程安全吗?,c#,synchronization,locking,C#,Synchronization,Locking,我知道使用lock(this)或任何共享对象是错误的 我想知道这种用法是否合适 public class A { private readonly object locker = new object(); private List<int> myList; public A() { myList = new List<int>() } private void MethodeA() { lock(locker) {

我知道使用
lock(this)
或任何共享对象是错误的

我想知道这种用法是否合适

public class A
{
  private readonly object locker = new object();
  private List<int> myList;
  public A()
  {
    myList = new List<int>()
  }

  private void MethodeA()
  {
    lock(locker)
    {
      myList.Add(10);
    }
  }

  public void MethodeB()
  {
    CallToMethodInOtherClass(myList);
  }
}

public class OtherClass
{
  private readonly object locker = new object();
  public CallToMethodInOtherClass(List<int> list)
  {
   lock(locker)
   {
     int i = list.Count;
   }
  }
}
公共A类
{
私有只读对象锁定器=新对象();
私人名单;
公共A()
{
myList=新列表()
}
私有无效方法a()
{
锁(储物柜)
{
添加(10);
}
}
公共无效方法b()
{
CallToMethodOtherClass(myList);
}
}
公共类其他类
{
私有只读对象锁定器=新对象();
公共调用ToMethodOtherClass(列表)
{
锁(储物柜)
{
int i=list.Count;
}
}
}

这个线安全吗?在
OtherClass
中,我们使用私有对象进行锁定,因此如果
class a
使用其私有锁进行锁定,那么
OtherClass
中的锁块中的列表是否仍会更改?

否,这是线程安全的

您的两个方法正在锁定两个不同的对象,它们不会相互锁定

因为
callToMethodOtherClass()
只检索Count的值,所以不会出现可怕的错误。但是它周围的
lock()
是无用的和误导性的

如果该方法会在列表中进行更改,那么您将遇到一个严重的问题。要解决此问题,请更改方法B:

  public void MethodeB()
  {
    lock(locker)  // same instance as MethodA is using
    {
      CallToMethodInOtherClass(myList);
    }
  }

不,他们必须锁定同一个对象。使用您的代码,它们都锁定在不同的位置,并且每个调用都可以同时执行


要使代码线程安全,请在MethodB中放置锁或将列表本身用作锁对象。

否,它不是线程安全的。加法和计数可以“同时”执行。您有两个不同的锁定对象

传递列表时始终锁定自己的锁定对象:

  public void MethodeB()
  {
    lock(locker)
    {
      CallToMethodInOtherClass(myList);
    }
  }

不,这不是线程安全的
A.MethodeA
OtherClass.CallToMethodInOtherClass
锁定在不同的对象上,因此它们不是互斥的。如果您需要保护对列表的访问,请不要将其传递给外部代码,而是将其保持为私有。

这可能是最简单的方法

public class A
{
  private List<int> myList;
  public A()
  {
    myList = new List<int>()
  }

  private void MethodeA()
  {
    lock(myList)
    {
      myList.Add(10);
    }
  }

  public void MethodeB()
  {
    CallToMethodInOtherClass(myList);
  }
}

public class OtherClass
{
  public CallToMethodInOtherClass(List<int> list)
  {
   lock(list)
   {
     int i = list.Count;
   }
  }
}
公共A类
{
私人名单;
公共A()
{
myList=新列表()
}
私有无效方法a()
{
锁(myList)
{
添加(10);
}
}
公共无效方法b()
{
CallToMethodOtherClass(myList);
}
}
公共类其他类
{
公共调用ToMethodOtherClass(列表)
{
锁(列表)
{
int i=list.Count;
}
}
}

所有答案都说这些是不同的锁对象

一种简单的方法是使用静态锁对象f.ex:

publc class A
{
    public static readonly object lockObj = new object();
}
在这两个类中都使用类似于锁的锁:

lock(A.lockObj)
{
}

不,这不是线程安全的。为了保证线程安全,您可以在
静态
对象上使用lock,因为它们在线程之间共享,这可能会导致代码死锁,但可以通过保持适当的锁定顺序来处理。
lock
会带来性能成本,因此请明智地使用它


希望这有帮助

许多答案都提到使用静态只读锁

但是,您确实应该尝试避免这种静态锁定。在多个线程使用静态锁的情况下,很容易创建死锁

您可以使用.NET4并发集合中的一个,它们确实为您提供了一些线程同步,因此您不需要使用锁定

看看
System.collections.Concurrent
名称空间。
对于本例,您可以使用
ConcurrentBag
类。

它实际上是线程安全的(纯粹是关于
Count
的实现细节),但是:

  • 线程安全的代码片段不是线程安全应用程序生成的。您可以将不同的线程安全操作组合成非线程安全操作。事实上,许多非线程安全的代码可以分解成更小的片段,所有这些片段本身都是线程安全的

  • 由于您希望的原因,它不是线程安全的,这意味着进一步扩展它将不是线程安全的

  • 此代码将是线程安全的:

    public void CallToMethodInOtherClass(List<int> list)
    {
       //note we've no locks!
       int i = list.Count;
       //do something with i but don't touch list again.
    }
    
    public void CallToMethodInOtherClass(List<int> list)
    {
      Console.WriteLine(list[93]); // obviously only works if there's at least 94 items
                                // but that's nothing to do with thread-safety
    }
    
    public void CallToMethodInOtherClass(List<int> list)
    {
       lock(locker)//same as in the question, different locker to that used elsewhere.
       {
         int i = list.Count;
         if(i > 93)
           Console.WriteLine(list[93]);
       }
    }
    
    此代码不是线程安全的:

    public void CallToMethodInOtherClass(List<int> list)
    {
       //note we've no locks!
       int i = list.Count;
       //do something with i but don't touch list again.
    }
    
    public void CallToMethodInOtherClass(List<int> list)
    {
      Console.WriteLine(list[93]); // obviously only works if there's at least 94 items
                                // but that's nothing to do with thread-safety
    }
    
    public void CallToMethodInOtherClass(List<int> list)
    {
       lock(locker)//same as in the question, different locker to that used elsewhere.
       {
         int i = list.Count;
         if(i > 93)
           Console.WriteLine(list[93]);
       }
    }
    
    这个类保证它所做的一切都是线程安全的。在不依赖任何实现细节的情况下,这里没有任何方法会因为另一个线程对同一实例所做的操作而损坏状态或给出错误的结果。但以下代码仍然不起作用:

    // (l is a ThreadSafeList visible to multiple threads.
    if(l.Count > 0)
      Console.WriteLine(l[0]);
    
    我们已经100%保证了每次调用的线程安全性,但是我们没有保证组合,也不能保证组合

    我们可以做两件事。我们可以为组合添加一个方法。对于许多专门为多线程使用而设计的类来说,以下内容是常见的:

    public bool TryGetItem(int index, out int value)
    {
      lock(locker)
      {
        if(l.Count > index)
        {
          value = l[index];
          return true;
        }
        value = 0;
        return false;
      }
    }
    
    这使得计数测试和项目检索成为保证线程安全的单个操作的一部分

    或者,我们通常需要做的是,在操作分组的地方锁定:

    lock(lockerOnL)//used by every other piece of code operating on l
      if(l.Count > 0)
        Console.WriteLine(l[0]);
    
    当然,这会使
    ThreadSafeList
    中的锁变得多余,浪费精力、空间和时间。这是大多数类不在其实例成员上提供线程安全性的主要原因——因为您无法从类内有意义地保护对成员的调用组,所以尝试这样做是浪费时间的,除非线程安全承诺本身得到了很好的指定和使用

    回到问题中的代码:

    除非
    OtherClass
    有自己的内部锁定原因,否则应移除
    CallToMethodInOtherClass
    中的锁定。它不能做出一个有意义的承诺,即它不会以非线程安全的方式进行组合,而向程序添加更多锁只会增加分析程序以确保没有死锁的复杂性

    CallToMethodOtherClass
    的调用应受到与该类中其他操作相同的锁的保护:

    public void MethodeB()
    {
      lock(locker)
        CallToMethodInOtherClass(myList);
    }
    
    然后只要
    calltomethodinoth