为什么可以在同一个类中创建的另一个线程中访问局部变量?

phi*_*131 14 c# multithreading local-variables

我真的找不到关于这个确切主题的任何内容,所以如果问题已经存在,请引导我走向正确的方向.

根据我对.NET的了解,不可能跨不同的线程访问变量(如果该语句错误,请纠正我,这正是我在某处读到的).

然而,现在在这个代码示例中,它似乎不应该工作:

class MyClass
{
    public int variable;

    internal MyClass()
    {
        Thread thread = new Thread(new ThreadStart(DoSomething));
        thread.IsBackground = true;
        thread.Start();
    }

    public void DoSomething()
    {
        variable = 0;
        for (int i = 0; i < 10; i++)
            variable++;

        MessageBox.Show(variable.ToString());
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void SomeMethod();
    {
        MyClass mc = new MyClass();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行时SomeMethod()不应该.NET抛出异常,因为创建的对象mc运行在与mc-initializer中创建的线程不同的线程中,并且这个新线程正试图访问本地变量mc

这些MessageBox节目10是(不)预期的,但我不确定为什么这应该有效.

也许我不知道要搜索什么,但我找不到任何线程主题,会解决这个问题,但也许我对变量和线程的想法是错误的.

Eri*_*ert 47

根据我对.NET的了解,不可能跨不同的线程访问变量.如果该陈述是错误的,请纠正我,这正是我在某处读到的.

这句话完全是假的,所以请考虑一下你的修正.

您可能在某处读过不能在不同线程上访问局部变量的地方.该陈述也是错误的,但通常是陈述.正确的说法是那些不是的局部变量

  • 在异步方法中
  • 在迭代器块中(即带yield return或的方法yield break)
  • 封闭的匿名函数的外部变量

多个线程无法访问.甚至那个说法有点狡猾; 有方法可以用指针和unsafe代码块来做到这一点,但尝试这样做是一个非常糟糕的主意.

我还注意到你的问题询问了局部变量,但后来给出了一个字段的例子.根据定义,字段不是局部变量.根据定义,局部变量是方法体的本地变量.(或构造函数体,索引器主体等)确保您清楚.本地的定义特征不是它"在堆栈中"或某种类似的东西; 本地的"本地"部分是它的名称在方法体外没有意义.

在一般情况下:变量是指存储器存储位置.线程是进程中的控制点,进程中的所有线程共享相同的内存; 这就是使他们成为线程而不是进程的原因.因此,通常情况下,所有变量都可以由多个线程在所有时间和所有顺序中访问,除非采用某种机制来防止这种情况发生.

让我再说一遍,只是为了确保它在你的脑海中绝对清晰:考虑单线程程序的正确方法是所有变量都是稳定的,除非有些东西使它们发生变化.考虑多线程程序的正确方法是所有变量都在不按特定顺序进行变异,除非某些变量保持静止或有序. 这是多线程共享内存模型如此困难的根本原因,也就是为什么要避免它.

在您的特定示例中,两个线程都可以访问this,因此两个线程都可以看到该变量this.variable.您没有实现任何机制来阻止这种情况,因此两个线程都可以按任何顺序写入和读取该变量,但实际上受到的限制非常少.您可以实现以驯服此行为的一些机制是:

  • 将变量标记为ThreadStatic.这样做会导致在每个线程上创建一个新变量.
  • 将变量标记为volatile.这样做会对可能观察到的读取和写入顺序施加某些限制,并且还会对编译器或CPU可能导致意外结果的优化施加某些限制.
  • lock语句围绕变量的每次使用.
  • 首先不要共享变量.

除非您对多线程和处理器优化有深入的了解,否则我建议除了后者之外的任何选项.

现在,假设您确实希望确保在另一个线程上对变量的访问失败.您可以让构造函数捕获创建线程的线程ID并将其存储起来.然后,您可以通过属性getter/setter访问该变量,其中getter和setter检查当前线程ID,如果它与原始线程ID不同,则抛出异常.

基本上它的作用是滚动你自己的单线程公寓线程模型."单线程单元"对象是一个只能在创建它的线程上合法访问的对象.(你买一台电视,你把它放在你的公寓里,只允许你公寓里的人看你的电视.)单线程公寓与多线程公寓和免费线程的细节相当复杂; 有关更多背景,请参阅此问题.

你能解释一下STA和MTA吗?

这就是为什么,例如,您必须永远不能从工作线程访问您在UI线程上创建的UI元素; UI元素是STA对象.


Eri*_* J. 8

根据我对.NET的了解,不可能跨不同的线程访问变量(如果该语句错误,请纠正我,这正是我在某处读到的).

这是不正确的.可以从范围内的任何位置访问变量.

从多个线程访问相同的变量时需要谨慎,因为每个线程可以在非确定性时间对变量进行操作,从而导致细微的,难以解决的错误.

有一个出色的网站,涵盖从基础到高级概念的.NET中的线程.

http://www.albahari.com/threading/

  • 线程和变量范围是不同的概念.您可以在类中创建一个启动5个线程的方法,并且每个线程都可以访问该类中的任何变量. (2认同)

Edu*_*tru 5

我有点晚了,@ Eric J.给出的答案非常精彩而且非常重要.

我只想在你对线程和变量的看法中为另一个问题添加一些清晰度.

你在你的问题的标题"在另一个线程中访问变量"中说过这个.除此之外,在您的代码中,您正在从1个线程访问您的变量,这是在此处创建的线程:

    Thread thread = new Thread(new ThreadStart(DoSomething));
    thread.IsBackground = true;
    thread.Start();
Run Code Online (Sandbox Code Playgroud)

所有这些事情让我意识到你害怕与实际创建实例的线程不同的线程MyClass将使用来自该实例内部的东西.

以下事实对于更清晰地了解多线程是什么非常重要(它比您想象的更简单):

  • 线程不拥有变量,它们拥有堆栈,堆栈可能包含一些变量,但这不是我的观点
  • 创建类的实例的线程与该线程之间没有内在联系.它由所有线程拥有,就像它们不属于任何线程一样.
  • 当我说这些事情时,我不是在谈论线程堆栈,但有人可能会说线程和实例是两组独立的对象,只是为了更大的好处而互动:)

编辑

我看到线程安全这个词出现在这个答案的主题上.万一你可能想知道这些词是什么意思我推荐这篇由@Eric Lippert撰写的精彩文章:http: //blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing -你的呼叫线程safe.aspx