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

时间:2009-01-07 16:06:44

标签: c# concurrency static-methods

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

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

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

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

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

我打电话给Class.addOne(5);和Class.addOne(6);同时,我可能会返回6或7,具体取决于谁首先调用foo? (即竞争条件)

11 个答案:

答案 0 :(得分:52)

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. 

答案 1 :(得分:38)

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

 class BadCounter
 {
       private static int counter;

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

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

什么是线程安全的细节可能非常棘手,但总的来说如果你没有改变任何状态(除了传递给你的东西,无论如何 - 那里有一些灰色区域)然后它通常没关系。

答案 2 :(得分:22)

答案 3 :(得分:7)

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

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没有任何意义,但是嘿,这是一个样本。

答案 4 :(得分:3)

如果它正在修改函数外部的某个变量,那么这只是一个竞争条件。你的例子不是那样做的。

这基本上就是你所期待的。线程安全意味着该功能:

  1. 不修改外部数据,或
  2. 正确同步对外部数据的访问,以便任何时候只有一个功能可以访问它。
  3. 外部数据可以是存储(数据库/文件)中的东西,也可以是应用程序内部的东西(变量,类的实例等):基本上在世界任何地方声明的任何东西都在功能的范围。

    你的函数的非线程安全版本的一个简单例子是:

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

    如果你在没有同步的情况下从两个不同的线程调用它,查询myVar的值将会有所不同,具体取决于在对addOne的所有调用完成后查询是否发生,或者查询是否发生在两次调用之间,或查询是否发生在任何一个电话之前。

答案 5 :(得分:3)

有一些研究允许您检测非线程安全的代码。例如。项目CHESS at Microsoft Research

答案 6 :(得分:2)

在上面的例子中。

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

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而不是预期的6.:O

答案 7 :(得分:1)

在任何地方,thread safe表示在访问资源时没有两个或多个线程发生冲突。通常静态变量 - 用C#,VB.NET和Java等语言 - 使你的代码线程不安全

在Java中存在 synchronized 关键字。但是在.NET中你得到汇编选项/指令:


class Foo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Bar(object obj)
    {
        // do something...
    }
}

非线程安全类的示例应该是单例,具体取决于此模式的编码方式。通常它必须实现 synchronized 实例创建者。

如果您不想使用 synchronized方法,可以尝试使用锁定方法,例如 spin-lock

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

并发或顺序调用之间不共享

foo,因此addOne是线程安全的。

答案 10 :(得分:0)

'foo'和'someNumber'在你的例子中是安全的原因是它们驻留在堆栈上,每个线程都有自己的堆栈,因此不会共享。

只要数据有可能被共享,例如,全局或共享指向对象的指针,那么您可能会遇到冲突,并且可能需要使用某种类型的锁。