【问题标题】:Static method local variables and thread-safety静态方法局部变量和线程安全
【发布时间】:2010-02-22 22:39:07
【问题描述】:

对于普通的实例方法,局部变量是线程安全的。

如果我在静态方法中有以下内容:

int i = 0;
i += 3;

这会是线程安全的吗?有什么问题吗?

另外,当每个变量都有自己的堆栈时,这到底意味着什么?这是否意味着它自己的堆栈跟踪?

谢谢

【问题讨论】:

  • 参见:stackoverflow.com/questions/420895/… 如果i 不是局部变量,那么问题就来了。
  • 或者如果他有另一个对象在那里做线程不安全的事情,比如设置没有锁或其他东西的静态字段。按照逻辑,他的方法也不是线程安全的。但这更像是一个棘手的例外:)
  • 好评论 skurmedal。感谢 Seth 的链接,不幸的是,有时我在提交我的帖子后一段时间才会看到相关链接。

标签: c# static


【解决方案1】:

如果您发布的行在方法内部,那么它没有理由不应该是线程安全的。线程没有以任何方式交互 - 每个线程看到不同的i。

如果您尝试在线程之间共享 i 的值,例如通过将 i 设为静态字段,就会遇到问题。然后就有可能获得一个比赛条件,根据时间的不同,你会得到不同的结果。

关于第二个问题,每个线程都有自己的堆栈,而不是每个变量。

【讨论】:

    【解决方案2】:

    首先,我假设“线程安全”是指“当多个线程上发生突变时,表现得好像某些操作是原子的”。如果这不是您所说的“线程安全”的意思,那么请在询问有关它的问题之前仔细定义“线程安全”。似乎几乎每个提出堆栈溢出线程安全问题的人都有不同的个人定义。

    第二,局部变量不是线程安全的。特别是,作为 lambda 或匿名方法的封闭外部变量的局部变量,或者位于迭代器块内的局部变量,在多个线程上发生变异时,不能保证是线程安全的。

    不是匿名函数的封闭外部变量且不在迭代器块中的局部变量只能由当前线程变异,因此不会被多个线程同时变异。

    另外,当每个变量都有自己的堆栈时,这到底意味着什么?

    我不知道那是什么意思; 变量没有堆栈。 线程有栈。

    【讨论】:

      【解决方案3】:

      简短的回答是肯定的,以下方法是线程安全的:

      public static int CalcuateSomething( int number ) {
        int i = number * 10;
        return i;
      }
      

      只要不指向堆上的共享对象,所有局部变量都是安全的。

      就像您在单线程应用程序的情况下一样,此方法的以下调用将返回不同的值。

      CalculateSomething( 10 ) => 100
      CalculateSomething( 20 ) => 200
      

      这是为什么呢?因为方法编号的每次调用都采用不同的值,因此我也会这样做。函数结束后不会记住 i 的值,因为 i 是在堆栈上分配的。在几乎所有语言中,函数都是堆栈模型。每次调用不同的方法时,当前方法都会暂停。一个新方法被压入调用堆栈并被调用。当该方法完成时,程序取消暂停调用方法并从它停止的地方恢复(即在 returnAddress 处)。在该方法中定义的任何局部变量都是该方法的堆栈帧的一部分。我们上面方法的堆栈帧可以这样想:

      public Class StackFrameForCalculateSomething implements StackFrame {
        public int ReturnAddress;
        public int i = 0;
        public int number;
      }
      

      堆栈可以被认为是 StackFrame 对象的集合。

      堆栈调用堆栈

      每次调用新方法时,程序可能会执行以下操作:

      StackFrameForCalculcateSomething s = new StackFrameForCalculateSomething();
      s.returnAddress = instructionPointer;
      s.number = 10;
      callStack.push( s );
      s.invoke();
      
      StackFrameForCalculcateSomething s2 = new StackFrameForCalculateSomething();
      s2.returnAddress = instructionPointer;
      s2.number = 20;
      callStack.push( s2 );
      s2.invoke();
      

      这对线程意味着什么?好吧,在线程的情况下,您将拥有多个具有自己集合的独立 callStack。访问局部变量是安全的原因是线程 1 的 callStack 无法访问线程 2 的 callStack,因为它们是单独的对象。就像在单线程情况下 s 和 s2 是具有不同数字值的不同对象。因此它们彼此独立。考虑 s 在线程 1 中,而 s2 在线程 2 中。线程 1 和线程 2 不共享任何内存,因此它是线程安全的。

      线程不共享它们的堆栈帧。他们确实共享堆。

      【讨论】:

        【解决方案4】:

        这是一个带有(简单的)示例的演示,以帮助扩展 Mark 所说的内容:

        void Main()
        {   
            for (int x = 0; x < 10; x++)
            {
                ThreadPool.QueueUserWorkItem(z=> myClass.DoSomething());
            }
        
        }
        public class myClass
        {
            public static void DoSomething()
            {
                int i = 0;
                Console.WriteLine (i += 3);
            }
        }
        

        输出: 3 3 3 3 3 3 3 3 3 3 3

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-12-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-07
          • 1970-01-01
          相关资源
          最近更新 更多