当前位置:  开发笔记 > 编程语言 > 正文

我怎么知道这个C#方法是否是线程安全的?

如何解决《我怎么知道这个C#方法是否是线程安全的?》经验,为你挑选了4个好方法。

我正在为ASP.NET缓存项目删除事件创建一个回调函数.

文档说我应该调用一个对象或调用我知道将存在的调用(将在范围内),例如静态方法,但它说我需要确保静态是线程安全的.

第1部分:我可以采取哪些措施使其非线程安全?

第2部分:这是否意味着,如果我有

static int addOne(int someNumber){
    int foo = someNumber;
    return foo +1; 
}

我叫Class.addOne(5); 和Class.addOne(6); 同时,我可能会返回6或7,具体取决于谁首先调用foo?(即比赛条件)



1> Cybis..:

addOne功能确实是线程安全的,因为它不访问可能被其他线程访问的任何数据.局部变量不能在线程之间共享,因为每个线程都有自己的堆栈.但是,您必须确保函数参数是值类型而不是引用类型.

static void MyFunction(int x) { ... } // thread safe. The int is copied onto the local stack.

static void MyFunction(Object o) { ... } // Not thread safe. Since o is a reference type, it might be shared among multiple threads. 



2> Jon Skeet..:

不,addOne在这里是线程安全的 - 它只使用局部变量.这是一个不是线程安全的例子:

 class BadCounter
 {
       private static int counter;

       public static int Increment()
       {
             int temp = counter;
             temp++;
             counter = temp;
             return counter;
       }
 }

这里,两个线程可以同时调用Increment,最后只增加一次.(return ++counter;顺便说一下,使用也一样糟糕 - 以上是同一件事的更明确的版本.我扩展了它,因此更明显是错误的.)

什么是和不是线程安全的细节可能是相当棘手的,但一般来说,如果你没有改变任何状态(除了传递给你的东西,无论如何 - 那里的灰色区域)那么它通常是好的.



3> Rob Parker..:

线程问题(我最近也一直在担心)是由于使用带有独立缓存的多个处理器内核以及基本的线程交换竞争条件.如果单独内核的高速缓存访​​问相同的内存位置,他们通常不会知道另一个内存位置,并且可能会单独跟踪该数据位置的状态,而不会返回到主内存(甚至是所有共享的同步高速缓存)出于处理器性能原因,例如,L2或L3处的核心.因此,即使是执行顺序的联锁技巧在多线程环境中也可能不可靠.

您可能知道,纠正此问题的主要工具是锁,它提供独占访问机制(在同一锁的争用之间)并处理底层缓存同步,以便通过各种锁保护访问相同的内存位置代码部分将被正确序列化.你可以在何时以及以什么顺序获得锁定之间存在竞争条件,但是当你可以保证锁定部分的执行是原子的时(在该锁的上下文中),这通常更容易处理.

你可以锁定任何引用类型的实例(例如,继承自Object,而不是像int或enums这样的值类型,而不是null),但是理解对象上的锁对访问没有固有影响是非常重要的.对于该对象,它仅与其他尝试相互作用以获取对同一对象的锁定.由类使用适当的锁定方案来保护对其成员变量的访问.有时实例可以通过锁定自身来保护对自己成员的多线程访问(例如lock (this) { ... }),但通常这不是必需的,因为实例往往只由一个所有者持有,并且不需要保证对该实例的线程安全访问.

更常见的是,类创建一个私有锁(例如,private readonly object m_Lock = new Object();用于保护对每个实例的单独锁以保护对该实例的成员的访问,或者private static readonly object s_Lock = new Object();用于中央锁以保护对该类的静态成员的访问).Josh有一个更具体的使用锁的代码示例.然后,您必须对类进行编码以适当地使用锁.在更复杂的情况下,您甚至可能希望为不同的成员组创建单独的锁,以减少对未使用的不同类型资源的争用.

因此,为了使它回到原始问题,只访问其自己的局部变量和参数的方法将是线程安全的,因为它们存在于特定于当前线程的堆栈上它们自己的内存位置,并且无法访问其他 - 除非您在传递它们之前在线程之间共享这些参数实例.

只访问实例自己的成员(没有静态成员)的非静态方法 - 当然还有参数和局部变量 - 不需要在单个所有者使用的该实例的上下文中使用锁(不是需要是线程安全的,但如果实例是为了共享并希望保证线程安全访问,则实例需要使用一个或多个特定于该实例的锁来保护对其成员变量的访问(锁定在实例本身就是一个选项) - 而不是将它留给调用者在共享不是为了线程安全可共享的东西时在它周围实现自己的锁.

访问那些从未被操作的只读成员(静态或非静态)通常是安全的,但是如果它所拥有的实例本身不是线程安全的,或者如果你需要保证多次操作它的原子性,那么你可能需要使用您自己的锁定方案保护对它的所有访问.如果实例使用锁定本身就可以很方便,因为你可以简单地通过多次访问来实现对实例的锁定以获得原子性,但如果它是单一访问则不需要这样做.在自身上使用锁来使这些访问单独是线程安全的.(如果不是你的班级,你必须知道它是自己锁定还是使用你无法从外部访问的私人锁.)

最后,可以从实例中访问更改静态成员(由给定方法或任何其他方法更改) - 当然还有访问这些静态成员的静态方法,可以随时随地从任何人调用 - 这些方法具有最大的需要使用负责任的锁定,没有它肯定不是线程安全的,并可能导致不可预测的错误.

在处理.NET框架类时,MS在MSDN中记录给定的API调用是否是线程安全的(例如,所提供的泛型集合类型的静态方法List是线程安全的,而实例方法可能不是 - 但是专门检查确定).绝大多数时候(除非它特别说它是线程安全的),它不是内部线程安全的,所以你有责任以安全的方式使用它.即使在内部线程安全的情况下实现单个操作,如果代码执行任何需要原子的更复杂的操作,您仍然需要担心代码的共享和重叠访问.

一个重要的警告是迭代一个集合(例如with foreach).即使对集合的每次访问都获得稳定状态,也没有固有的保证,它不会在这些访问之间发生变化(如果其他任何地方都可以访问它).当集合保存在本地时通常没有问题,但是可以更改的集合(通过另一个线程或在循环执行期间!)可能会产生不一致的结果.解决此问题的一种简单方法是使用原子线程安全操作(在保护性锁定方案内)来制作集合的临时副本(MyType[] mySnapshot = myCollection.ToArray();然后迭代锁外的本地快照副本.在许多情况下,这避免了一直持有锁的需要,但是根据你在迭代中所做的事情,这可能是不够的,你只需要保护整个时间的变化(或者你可能已经在里面一个锁定的部分,防止访问改变集合以及其他东西,所以它被覆盖).

因此,线程安全设计有一点艺术,并且知道锁定以保护事物的位置和方式在很大程度上取决于您的类的整体设计和使用.可能很容易变得偏执,并认为你必须为所有事情获得锁定,但实际上它是关于找到保护事物的正确层.


我不明白你刚才说的一句话,但努力的+1.
你应该开一个博客,那么你就不需要写问题的借口来写文章了!

4> JoshBerke..:

你的方法很好,因为它只使用局部变量,让我们稍微改变你的方法:

static int foo;

static int addOne(int someNumber)
{
  foo=someNumber; 
  return foo++;
}

这不是一个线程安全的方法,因为我们正在触摸静态数据.然后需要将其修改为:

static int foo;
static object addOneLocker=new object();
static int addOne(int someNumber)
{
  int myCalc;
  lock(addOneLocker)
  {
     foo=someNumber; 
     myCalc= foo++;
  }
  return myCalc;
}

我认为这是一个愚蠢的样本,如果我正确地阅读它会导致foo没有任何意义,但是嘿,这是一个样本.

推荐阅读
喜生-Da
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有