web-dev-qa-db-ja.com

同じクラスで作成された別のスレッドでローカル変数にアクセスできるのはなぜですか?

私はこの正確なトピックについて何も本当に見つけることができなかったので、質問がすでに存在する場合は、正しい方向に私を導いてください。

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

作成したオブジェクトmcmc- initializer内で作成されたスレッドとは別のスレッドで実行されているため、SomeMethod()を実行しても.NETは例外をスローしません。この新しいスレッドはmcのローカル変数にアクセスしようとしていますか?

MessageBoxは_10_を期待どおり(そうではない)示していますが、これが機能する理由がわかりません。

たぶん、何を検索すればいいかわからなかったが、見つけられたスレッディングトピックではこの問題に対処できなかったかもしれないが、変数とスレッドに関する私の考えが間違っているかもしれない。

14
phil13131

.NETについて学んだことから、異なるスレッド間で変数にアクセスすることはできません。その記述が間違っている場合は訂正してください。それはどこかで読んだものです。

そのステートメントは完全に偽ですので、これはあなたの修正を考慮してください。

ローカル変数が異なるスレッド間でアクセスできないことをおそらくどこかで読んだでしょう。そのステートメントは also falseですが、一般的に述べられています。正しいステートメントは、

  • 非同期メソッドで
  • イテレーターブロック内(つまり、yield returnまたはyield breakを持つメソッド)
  • 無名関数の閉じられた外部変数

複数のスレッドからアクセスすることはできません。そして、その主張でさえ少し危険です。ポインタとunsafeコードブロックでこれを行う方法はありますが、そうすることは非常に悪い考えです。

また、あなたの質問はローカル変数について尋ねていますが、 field の例を示しています。フィールドは、定義上、ローカル変数ではありません。ローカル変数は、定義によりメソッド本体にローカルです。 (またはコンストラクター本体、インデクサー本体など)。そのことを明確にしてください。ローカルの定義的な特徴は、それが「スタック上」またはそのようなものであることではありません。ローカルの「ローカル」部分は、その名前がメソッド本体の外では意味がないということです。

一般的な場合:変数はストレージロケーションであり、 memory を参照します。スレッドはプロセス内の制御点であり、プロセス内のすべてのスレッドは同じメモリを共有します。それが threads であり、 processes ではありません。したがって、一般に、すべての変数は、いつでもすべての順序で複数のスレッドからアクセスできます。ただし、それを防ぐためのメカニズムが導入されている場合を除きます

繰り返しになりますが、あなたの心の中でそれが絶対にはっきりしていることを確認するために:シングルスレッドプログラムについて考える正しい方法は、何かがそれらを作らない限り、すべての変数が stable であるということです変化する。マルチスレッドプログラムについて考える正しい方法は、すべての変数が常に変化している特定の順序がない何かがまだ維持している場合を除いてまたは整然とした。 これが、マルチスレッディングの共有メモリモデルが非常に難しい基本的な理由です。したがって、それを避ける必要があります。

特定の例では、両方のスレッドがthisにアクセスできるため、両方のスレッドが変数this.variableを参照できます。これを防止するメカニズムを実装していないため、両方のスレッドがその変数に対して任意の順序で書き込みと読み取りを行うことができますが、実際にはほとんど制約がありません。この動作を制御するために実装できるメカニズムには、次のものがあります。

  • 変数にThreadStaticのマークを付けます。これにより、各スレッドで新しい変数が作成されます。
  • 変数にvolatileのマークを付けます。これを行うと、読み取りと書き込みの順序付けを監視する方法に特定の制限が課され、また、予期しない結果を引き起こす可能性があるコンパイラまたはCPUによる最適化にも特定の制限が課されます。
  • 変数のすべての使用法の前後にlockステートメントを置きます。
  • そもそも変数を共有しないでください。

マルチスレッディングとプロセッサの最適化について deep を理解していない限り、後者以外のオプションはお勧めしません。

ここで、別のスレッドで変数へのアクセスが失敗したことを確認したいとします。コンストラクターに作成スレッドのスレッドIDをキャプチャさせ、それを隠しておくことができます。次に、プロパティのゲッター/セッターを介して変数にアクセスし、ゲッターとセッターが現在のスレッドIDをチェックし、元のスレッドIDと異なる場合は例外をスローします。

基本的に、これは自分のシングルスレッドアパートメントスレッドモデルをロールします。 「シングルスレッドアパートメント」オブジェクトは、それを作成したスレッドでのみ正当にアクセスできるオブジェクトです。 (あなたはテレビを購入し、あなたのアパートにそれを置きます、あなたのアパートの人々だけがあなたのテレビを見ることを許可されます。)シングルスレッドアパート対マルチスレッドアパート対フリースレッドの詳細は非常に複雑になります。詳細については、この質問を参照してください。

STAとMTAについて説明していただけますか?

これが、たとえば、UIスレッドで作成したUI要素にワーカースレッドからアクセスしてはならない理由です。 UI要素はSTAオブジェクトです。

47
Eric Lippert

私が.NETについて学んだことから、異なるスレッド間で変数にアクセスすることはできません(そのステートメントが間違っている場合は修正してください。それはどこかで読んだものです)。

不正解です。変数は、スコープ内のどこからでもアクセスできます。

複数のスレッドから同じ変数にアクセスする場合は注意が必要です。各スレッドは不確定な時間に変数に作用して、微妙で解決が難しいバグにつながる可能性があるためです。

基本から高度な概念まで、.NETのスレッディングをカバーする優れたWebサイトがあります。

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

8
Eric J.

私は少し遅れており、@ Eric J.の答えは素晴らしく、要点です。

スレッドと変数の認識に関する別の問題を少し明確にしたいと思います。

これは、質問のタイトル「別のスレッドで変数にアクセスできる」で述べています。これに加えて、コードでは、ここで作成されるスレッドである1スレッドから変数にアクセスしています:

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

これらすべてのことから、MyClassのインスタンスを実際に作成するスレッドとは別のスレッドが、そのインスタンス内の何かを使用するのではないかとおびえていることがわかりました。

次の事実は、マルチスレッドが何であるかをより明確に把握するために重要です(あなたが思ったよりも単純です)。

  • スレッドは変数を所有しておらず、スタックを所有しており、スタックにはいくつかの変数を含めることができますが、それは私の趣旨ではありません
  • クラスのインスタンスが作成されるスレッドとそのスレッドの間に固有の接続はありません。すべてのスレッドが所有していないのと同じ方法で、すべてのスレッドが所有しています。
  • 私がこれらのことを言うとき、私はスレッドスタックについて話しているのではありませんが、スレッドとインスタンスは2つの独立したオブジェクトのセットであり、単により良い目的のために相互作用するだけだと言うかもしれません:)

[〜#〜]編集[〜#〜]

thread safeという言葉がこの回答のスレッドに表示されます。これらの単語が何を意味するのか疑問に思われるかもしれませんが、@ Eric Lippertによるこの素晴らしい記事をお勧めします: http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is -this-thing-you-call-thread-safe.aspx

5
Eduard Dumitru

メモリの場所は単一のスレッドに分離されていません。もしそうなら、それは本当に不便でしょう。 CLRのメモリは アプリケーションドメイン 境界でのみ分離されます。 AppDomain ごとに各静的変数の個別のインスタンスがあるのはそのためです。ただし、 スレッドは特定の1つのアプリケーションドメインに関連付けられていません 。複数のアプリケーションドメインでコードを実行することも、何も実行しない(アンマネージコード)こともできます。彼らができないことは、同時に複数のアプリケーションドメインからコードを実行することです。これは、スレッドが2つの異なるアプリケーションドメインのデータ構造に同時にアクセスできないことを意味します。そのため、マーシャリング手法(MarshalByRefObjectなど)を使用するか、.NET RemotingやWCFなどの通信プロトコルを使用して、別のアプリケーションドメインからデータ構造にアクセスする必要があります。

CLRをホストするプロセスの次のユニコードアート図を検討してください。

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

各プロセスが複数のアプリケーションドメインを持つことができ、スレッドがそれらの複数のドメインからコードを実行できることがわかります。また、スレッドがアンマネージコードを実行できることを、左側と右側のAppDomainブロックの外側にも表示することで説明しました。

したがって、基本的にスレッドは、現在実行されている同じアプリケーションドメイン内の任意のデータ構造への重要なアクセスと重要なアクセスを持っています。ここでは、「重要な」という用語を使用して、あるクラスから別のクラスへのパブリック、保護、または内部メンバー。スレッドは、これが発生するのを決して防ぎません。ただし、リフレクションを使用すると、別のクラスのプライベートメンバーにもアクセスできます。それは私が重要なアクセスと呼んでいるものです。はい、それはあなたの側で少し多くの作業を伴いますが、リフレクションの呼び出しを完了した後は、特別なことは何も行われません(ちなみに、コードアクセスセキュリティによって許可される必要がありますが、それは別のトピックです)。ポイントは、スレッドが実行している同じアプリケーションドメイン内のほとんどすべてのメモリにアクセスできることです。

スレッドが同じアプリケーションドメイン内のほとんどすべてにアクセスできるのは、そうしないとめちゃくちゃに制限されるためです。マルチスレッド環境で作業する場合、開発者はクラス間でデータ構造を共有するために多くの追加の労力を費やす必要があります。

要点をまとめると:

  • データ構造(クラス/構造体)またはその構成メンバーとスレッドの間に1対1の関係はありません。
  • スレッドとアプリケーションドメインの間に1対1の関係はありません。
  • そして技術的には、OSスレッドとCLRスレッドの間に1対1の関係すらありません(実際には、そのアプローチから逸脱したCLIの主流の実装は知りません。1)。
  • 明らかに、CLRスレッドは、それが作成されたプロセスに限定されています。

1Singularity operating system であっても、.NETスレッドをオペレーティングシステムとハードウェアに直接マッピングしているように見えます

2
Brian Gideon

いいえ、後方にあります。データがまだスコープ内にある限り、データにアクセスできます。

反対の問題、つまり2つのスレッドが同時に同じデータにアクセスすることを防ぐ必要があります。これは競合状態と呼ばれます。これを防ぐためにlockのような同期手法を使用できますが、誤って使用すると、デッドロックが発生する可能性があります。

チュートリアルについては 。NETのC#スレッド を参照してください。

2
Karl Anderson