线程安全和局部变量

8
如果我有一个本地变量,就像这样:
Increment()
{
    int i = getFromDb(); // get count for a customer from db 
};

这是一个实例类,每当客户(即实例对象)进行购买时,它会被递增。这个变量是否线程安全?我听说局部变量是线程安全的,因为每个线程都有自己的堆栈等等。
此外,我想知道这个变量是否是共享状态?我的思维不足之处在于这个变量将与不同的客户对象(例如John、Paul等)一起使用,因此是线程安全的,但这种想法是有缺陷的,并且在并发编程方面缺乏经验。这听起来非常天真,但我在普通同步编码方面没有太多经验。
编辑:另外,函数调用getFromDb()不是问题的一部分,我不希望任何人猜测它的线程安全性,因为它只是一个调用,表示该值是从一个从数据库获取数据的函数中赋值而来的。 :)
编辑2:getFromDb的线程安全性得到了保证,因为它仅执行读操作。
7个回答

34

i被声明为本地(方法)变量,所以它通常只存在于Increment()的堆栈框架中 - 所以是的,i是线程安全的...(尽管我不能评论getFromDb)。

除非:

  • Increment是迭代器块(即使用yield returnyield break
  • i在匿名方法中使用(delegate { i = i + 1;})或lambda(foo => {i=i+foo;}

在上述两种情况下,有些情况可能会将其暴露在堆栈外部。但我怀疑你没有这样做。

请注意,字段(类上的变量)是线程安全的,因为它们很容易暴露给其他线程。这在static字段中更加明显,因为所有线程自动共享相同的字段(除了线程静态字段)。


讨论匿名方法和迭代器块。静态类和线程安全是最容易理解的概念,就我个人而言。 - GurdeepS
在我看来,重要的是要澄清即使在列出的例外情况下,局部变量仍然对每个方法调用都是唯一的。也就是说,虽然它们可以在方法上下文之外被访问,甚至可能以非线程安全的方式进行访问,但相对于同一方法的不同调用的上下文中的“相同”局部变量,它们仍然是线程安全的。 - Peter Duniho

5

你的声明有两个不同的部分 - 函数调用和赋值。

赋值是线程安全的,因为变量是局部的。每次调用该方法都会得到自己的本地变量版本,每个版本存储在内存中不同的堆栈帧中的不同位置。

getFromDb() 的调用可能是线程安全的 - 这取决于它的实现方式。


3
只要变量是方法内部的局部变量,它就是线程安全的。如果它是静态变量,那么默认情况下它就不是线程安全的。
class Example
{
  static int var1; //not thread-safe

  public void Method1()
   { int var2; //thread-safe
   }
}

1

i在语法上是线程安全的。但是当你将实例变量的值、实例方法的返回值赋给i时,共享数据就会被多个线程操纵。


1

虽然你的int i是线程安全的,但你整个情况可能不是线程安全的。正如你所说,你的int i是线程安全的,因为每个线程都有自己的堆栈跟踪,因此每个线程都有自己的i。然而,你的所有线程共享同一个数据库,因此你的数据库访问不是线程安全的。你需要正确地同步你的数据库访问,以确保每个线程只在正确的时刻看到数据库。

通常在并发和多线程中,如果你只读取信息,则不需要在DB上进行同步。但是,如果两个线程尝试从DB中读取/写入相同的信息集,则需要立即进行同步。


1

我将是“线程安全”的,因为每个线程都会在堆栈上拥有自己的i副本,就像您建议的那样。真正的问题是getFromDb()的内容是否是线程安全的?


1

i是一个局部变量,所以它不是共享状态。

如果你的getFromDb()是从一个oracle序列或者sql server自增字段读取,那么数据库会处理同步问题(在大多数情况下,不包括复制/分布式数据库),因此您可能可以安全地将结果返回给任何调用线程。也就是说,数据库保证每个getFromDB()调用都会得到一个不同的值。

线程安全通常需要一点工作 - 改变变量类型很少能让您获得线程安全,因为它取决于线程如何访问数据。通过重新设计算法,使用所有消费方都同步的队列来替代尝试编排一系列锁/监视器,可以避免一些麻烦。最好的解决方法是尽可能使算法无锁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接