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

我如何知道这个C#方法是否是线程安全的?,c#,concurrency,static-methods,C#,Concurrency,Static Methods,我正在为ASP.NET缓存项删除事件创建回调函数 文档中说我应该对一个对象调用一个方法,或者我知道将存在(将在作用域中)的调用,比如静态方法,但它说我需要确保静态是线程安全的 第1部分:我可以做些什么来保证它的线程安全 第二部分:这是否意味着如果我 static int addOne(int someNumber){ int foo = someNumber; return foo +1; } 我叫Class.addOne(5);加一类(6);同时,根据哪个调用首先设置了fo

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

文档中说我应该对一个对象调用一个方法,或者我知道将存在(将在作用域中)的调用,比如静态方法,但它说我需要确保静态是线程安全的

第1部分:我可以做些什么来保证它的线程安全

第二部分:这是否意味着如果我

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

我叫Class.addOne(5);加一类(6);同时,根据哪个调用首先设置了foo,我可能会得到6或7个返回值吗?(即竞争条件)

否,addOne在这里是线程安全的-它只使用局部变量。下面是一个线程不安全的示例:

 class BadCounter
 {
       private static int counter;

       public static int Increment()
       {
             int temp = counter;
             temp++;
             counter = temp;
             return counter;
       }
 }
在这里,两个线程可以同时调用Increment,并且最终只增加一次。 (顺便说一句,使用
return++counter;
也同样糟糕——上面是同样事情的一个更明确的版本。我扩展了它,所以它会更明显地错误。)


什么是线程安全的,什么不是线程安全的细节可能会很棘手,但一般来说,如果你没有改变任何状态(除了传递给你的状态——有点灰色区域),那么它通常是可以的。

任何可以由两个线程同时使用的对obect的访问都不是线程安全的


第2部分中的示例显然是安全的,因为它只使用作为参数传入的值,但如果使用对象范围的变量,则可能必须使用适当的锁定语句来包围访问

foo
在并发调用或顺序调用之间不共享,因此,
addOne
是线程安全的。

只有在修改函数外部的某个变量时,这才是竞争条件。你的例子不是这样做的

这基本上就是你想要的。线程安全意味着函数可以:

  • 不修改外部数据,或
  • 对外部数据的访问是正确同步的,因此在任何时候只有一个函数可以访问它
  • 外部数据可以是存储(数据库/文件)中保存的数据,也可以是应用程序内部的数据(变量、类实例等):基本上是在函数范围之外的世界任何地方声明的任何数据

    函数的非线程安全版本的一个简单示例如下:

    private int myVar = 0;
    
    private void addOne(int someNumber)
    {
       myVar += someNumber;
    }
    

    如果在没有同步的情况下从两个不同的线程调用此函数,则查询myVar的值将有所不同,这取决于查询是在所有addOne调用完成之后进行的,还是在两个调用之间进行的,或者查询发生在两个调用之前。

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

    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就没有意义了,但是嘿,这是一个例子。

    在上面的例子中没有

    线程安全性主要与存储状态有关。通过执行以下操作,可以使上述示例成为非线程安全的:

    static int myInt;
    
    static int addOne(int someNumber){
    myInt = someNumber;
    return myInt +1; 
    }
    
    这意味着由于上下文切换,线程1可能会调用myInt=someNumber,然后进行上下文切换,假设线程1只是将其设置为5。然后想象线程2进入并使用6,然后返回7。然后,当线程1再次唤醒时,它将在myInt中使用6而不是它正在使用的5,并返回7而不是预期的6O

    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. 
    

    在您的示例中,“foo”和“someNumber”之所以安全,是因为它们位于堆栈上,每个线程都有自己的堆栈,因此不共享


    一旦数据有可能被共享,例如,作为全局数据或共享指向对象的指针,则可能会发生冲突,并且可能需要使用某种类型的锁。

    正在进行一些研究,允许您检测非线程安全的代码。例如,project.

    Anywhere意味着在访问资源时不会有两个或多个线程发生冲突。通常,C#、VB.NET和Java等语言中的静态变量会使代码线程不安全

    在Java中存在synchronized关键字。但在.NET中,您可以获得程序集选项/指令:

    
    class Foo
    {
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Bar(object obj)
        {
            // do something...
        }
    }
    
    非线程安全类的示例应该是单例,具体取决于此模式的编码方式。通常,它必须实现一个同步的实例创建者

    如果您不想使用同步方法,可以尝试使用锁定方法,例如自旋锁。

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

    如您所知,纠正这种情况的主要工具是锁,它提供了一种独占访问机制(在同一锁的争用之间)并处理底层缓存同步,以便通过各种受锁保护的代码段正确序列化对同一内存位置的访问。您仍然可以在谁何时以什么顺序获得锁之间设置竞争条件,但处理w通常要简单得多