Why can a local variable be accessed in another thread created in the same class?

。_饼干妹妹 提交于 2019-12-03 03:54:44

问题


I couldn't really find anything on this exact topic, so please lead me toward the right direction, if a question already exists.

From what I have learned about .NET, it is not possible to access variables across different threads (please correct me if that statement is wrong, it's just what I have read somewhere).

Now in this codesample, however, it then seems that it shouldn't work:

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();
    }
}

When I run SomeMethod() shouldn't .NET throw an exception, because the created object mc is running in a different thread than the thread created within the mc-initializer and this new thread is trying to access the local variable of mc?

The MessageBox shows 10 as (not) expected, but I am not sure why this should work.

Maybe I didn't know what to search for, but no threading-topic I could find, would address this issue, but maybe my idea of variables and threads is wrong.


回答1:


From what I have learned about .NET, it is not possible to access variables across different threads. Please correct me if that statement is wrong, it's just what I have read somewhere.

That statement is completely false, so consider this your correction.

You probably read somewhere that local variables cannot be accessed across different threads. That statement is also false but is commonly stated. The correct statement is that local variables that are not

  • in an async method
  • in an iterator block (that is, a method with a yield return or yield break)
  • closed-over outer variables of an anonymous function

cannot be accessed by multiple threads. And even that claim is a bit dodgy; there are ways to do that with pointers and unsafe code blocks, but it is a very bad idea to attempt to do so.

I note also that your question asks about local variables but then gives an example of a field. A field is by definition not a local variable. A local variable is by definition local to a method body. (Or constructor body, indexer body, etc.) Make sure you are clear on that. The defining characteristic of a local is not that it is "on the stack" or some such thing; the "local" part of a local is that its name is not meaningful outside of the method body.

In the general case: a variable is a storage location that refers to memory. A thread is a point of control in a process, and all threads in a process share the same memory; that's what makes them threads and not processes. So in general, all variables can be accessed by multiple threads at all times and in all orders, unless some mechanism is put in place to prevent that.

Let me say that again just to make sure it is absolutely crystal clear in your mind: the correct way to think about a single-threaded program is that all variables are stable unless something makes them change. The correct way to think about a multi-threaded program is that all variables are mutating constantly in no particular order unless something is keeping them still or well-ordered. This is the fundamental reason why the shared-memory model of multithreading is so difficult, and therefore why you should avoid it.

In your particular example, both threads have access to this, and therefore both threads can see the variable this.variable. You have implemented no mechanisms to prevent that, and therefore both threads can be writing and reading to that variable in any order, subject to very few constraints indeed. Some mechanisms you could have implemented to tame this behavior are:

  • Mark the variable as ThreadStatic. Doing so causes a new variable to be created on each thread.
  • Mark the variable as volatile. Doing so imposes certain restrictions on how reads and writes may be observed to be ordered, and also imposes certain restrictions on optimizations made by the compiler or CPU that could cause unexpected results.
  • Put a lock statement around every usage of the variable.
  • Don't share the variable in the first place.

Unless you have a deep understanding of multithreading and processor optimizations, I recommend against any option except the latter.

Now, suppose you did wish to ensure that access to the variable failed on another thread. You could have the constructor capture the thread ID of the creating thread and stash it away. Then you could access the variable via a property getter/setter, where the getter and setter check the current thread ID, and throw an exception if it is not the same as the original thread ID.

Essentially what this does is rolls your own single threaded apartment threading model. An "single threaded apartment" object is an object that can only be accessed legally on the thread which created it. (You buy a TV, you put it in your apartment, only people in your apartment are allowed to watch your TV.) The details of single threaded apartments vs multithreaded apartments vs free threading get quite complicated; see this question for more background.

Could you explain STA and MTA?

This is why, for instance, you must never access a UI element that you create on the UI thread from a worker thread; the UI elements are STA objects.




回答2:


From what I have learned about .NET, it is not possible to access variables across different threads (please correct me if that statement is wrong, it's just what I have read somewhere).

That is not correct. A variable can be accessed from anywhere that it is in scope.

You need to exercise caution when accessing the same variable from multiple threads because each thread can act on the variable at a non-deterministic time leading to subtle, hard-to-resolve bugs.

There is an outstanding website that covers threading in .NET from the basics to advanced concepts.

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




回答3:


I'm a bit late and the answer @Eric J. gave is wonderful and to the point.

I just want to add a bit of clarity to another issue in your perception of threads and variables.

You said this in your question's title "variable be accessed in another thread". Adding to that is the fact that in your code, you're accessing your variable from exactly 1 thread which is the thread that gets created here:

    Thread thread = new Thread(new ThreadStart(DoSomething));
    thread.IsBackground = true;
    thread.Start();

All these things made me realize that you were scared that a thread different from the one that actually creates the instance of MyClass will use something from inside that instance.

The following facts are important for a clearer view of what multithreading is (it's simpler that you thought):

  • threads don't own variables, they own stacks and stack could contain some variables but that's not my point
  • there is no intrinsic connection between the thread on which an instance of a class gets created and that thread. It is owned by all threads the same way it is not owned by any of them.
  • when I say these things I'm not talking about thread stacks but one might say that threads and instances are two sets of independent objects which simply interact for the greater good :)

EDIT

I see the words thread safety appeared on this thread of answers. In case you're maybe wondering what those words mean I recommend this great article by @Eric Lippert: http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx




回答4:


No, you have it backwards, data is accessible as long as it is still in scope.

You need to guard against the opposite problem, two threads accessing the same data at the same time, which is called a race condition. You can use a synchronization technique like lock to prevent this from happening, but if used incorrectly it can lead to deadlock.

Read C# Threading in .NET for a tutorial.




回答5:


Memory locations are not isolated to a single thread. It would be really inconvenient if they were. Memory in the CLR is only isolated at the application domain boundary. That is why there is a separate instance of each static variable per AppDomain. However, threads are not tied to any one particular application domain. They can execute code in more than one application domain or none (unmanaged code). What they cannot do is execute code from more than one application domain at the same time. What this means is that a thread cannot simultaneously have access to data structures from two different application domains. That is why you have to use marshaling techniques (via MarshalByRefObject for example) or use communication protocols like .NET Remoting or WCF to gain access to data structures from another application domain.

Consider the following unicode art diagram of a process hosting the CLR.

┌Process───────────────────────────────┐
│                                      │
│ ┌AppDomain───┐        ┌AppDomain───┐ │
│ │            │        │            │ │ 
│ │       ┌──────Thread──────┐       │ │
│ │       │                  │       │ │
│ │       └──────────────────┘       │ │
│ │            │        │            │ │
│ └────────────┘        └────────────┘ │
└──────────────────────────────────────┘

You can see that each process can have more than one application domain and that a thread can execute code from more than just one of them. I have also tried illustrate the fact that a thread can execute unmanaged code as well by showing its existence outside of the left and right AppDomain blocks as well.

So basically a thread has trivial and non-trivial access to any data structures in the same application domain that it is currently executing in. I am using the term "trivial" here to include memory accesses (data structures, variables, etc.) via public, protected, or internal members from one class to another. A thread in no way prevents this from occurring. However, using reflection you could still gain access to even the private members of another class. That is what I am calling non-trivial access. Yes, it involves a bit more work on your part, but there is otherwise nothing fancy going on once you have completed the reflection calls (which by the way must be allowed by code access security, but that is a different topic). The point is that a thread has access to pretty much all of the memory in the same application domain it is executing in.

The reason why a thread has access to almost everything in the same application domain is because it would be insanely restrictive if it did not. Developers would have to put forth a lot of extra effort to share data structures between classes when working in a multithreaded environment.

So to sum up the salient points:

  • There is not a one-to-one relationship between a data structure (class/struct) or its constituent members and a thread.
  • There is not a one-to-one relationship between a thread and an application domain.
  • And technically there is not even a one-to-one relationship between an OS thread and a CLR thread (though in reality I know of no mainstream implementations of the CLI that deviate from that approach1).
  • Obviously a CLR thread is still confined to the process in which it was created.

1Even the Singularity operating system appears to directly map .NET threads to the operating system and hardware.



来源:https://stackoverflow.com/questions/18770414/why-can-a-local-variable-be-accessed-in-another-thread-created-in-the-same-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!