多线程共享局部变量

5

我希望能够深入了解创建调用类实例的相同方法的不同线程如何影响方法中的局部变量。

例如,我有一个只有一个方法(Divide)的类:

public class Maths
{
    public int Num1;
    public int Num2;

    public void Divide()
    {
        for (long i = 0; i < 100000; i++)
        {
            Num1 = 2;
            Num2 = 2;
            int result = Num1 / Num2;
            Num1 = 0;
            Num2 = 0;
        }
    }
}

实例化了两个线程并调用了以下的除法方法:

    static void Main(string[] args)
    {
        Maths m = new Maths();

        Task t1 = new Task(() => m.Divide());
        Task t2 = new Task(() => m.Divide());

        List<Task> tl = new List<Task> { t1, t2 };
        Parallel.ForEach(tl, task => task.Start());

        Console.ReadLine();
    }

}

有时这段代码运行良好。但是其他时候,它在以下行上抛出dividebyzero错误:
int result = Num1 / Num2;
我的假设是,其中一个线程正在重置Num1和Num2为零,就在另一个线程调用Num1 / Num2之前。因此导致除以零异常。
这很有道理,我应该使用锁,但我不明白这些局部变量Num1和Num2如何在线程之间共享,因为我的理解是局部变量不会在线程之间共享?

谢谢,现在我懂了。 - selams
1个回答

9
您说得对,局部变量不会在线程之间共享(通常情况下,每次调用方法时,其局部变量的新集合都会分配到执行线程的堆栈上,因此每个方法调用的局部变量完全独立,对其中一个进行修改不会影响其他变量)。
但是不幸的是,Num1Num2不是局部变量,而是字段。同一类实例的字段在线程之间共享。
如果要将它们声明为局部变量,则需要像这样声明:
public class Maths
{
    public void Divide()
    {
        int Num1;
        int Num2;

        for (long i = 0; i < 100000; i++)
        {
            Num1 = 2;
            Num2 = 2;
            int result = Num1 / Num2;
            Num1 = 0;
            Num2 = 0;
        }
    }
}

或者,您可以为每个线程创建单独的Maths类实例,这样每个线程将使用不同的Maths类实例的Num1Num2字段:

static void Main(string[] args)
{
    Maths m1 = new Maths();
    Maths m2 = new Maths();

    Task t1 = new Task(() => m1.Divide());
    Task t2 = new Task(() => m2.Divide());

    List<Task> tl = new List<Task> { t1, t2 };
    Parallel.ForEach(tl, task => task.Start());

    Console.ReadLine();
}

完美的答案并且解释得非常好! - Rainer Schaack
实际上,Main中的变量m是一个局部变量。它是指向堆对象的引用,这个引用不是完全共享的,但在两个线程的堆栈上是相等的。因此,锁定局部变量有时是有意义的。 - Papa Smurf

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