メソッドをスレッドセーフにするもの ルールは何ですか?


156

メソッドをスレッドセーフにするための全体的なルール/ガイドラインはありますか?おそらく100万回の単発的な状況があることを理解していますが、一般的にはどうですか?これは簡単ですか?

  1. メソッドがローカル変数にのみアクセスする場合、それはスレッドセーフです。

それですか?それは静的メソッドにも適用されますか?

@Cybisによって提供された1つの回答は次のとおりです。

各スレッドは独自のスタックを取得するため、ローカル変数はスレッド間で共有できません。

静的メソッドの場合もそうですか?

メソッドに参照オブジェクトが渡された場合、それはスレッドの安全性を損ないますか?私はいくつかの調査を行いましたが、特定のケースについてはたくさんありますが、いくつかのルールを使用するだけで、メソッドがスレッドセーフであることを確認するために従うガイドラインを定義できることを望んでいました。

したがって、私の究極の質問は、「スレッドセーフなメソッドを定義するルールの短いリストはありますか?ある場合、それは何ですか?」

編集
多くの良い点がここで作られました。この質問に対する本当の答えは、「スレッドの安全性を確保するための単純なルールはない」と思います。涼しい。いいよ。しかし、一般的に私は、受け入れられた答えが良い、短い要約を提供すると思います。常に例外があります。だからそれである。私はそれと共存できます。


59
ロックスなしで他のスレッドからもアクセスできる変数にはアクセスしないでください。
Hans Passant 2012年

4
ハンスの死者はイゴールに変わりました!
マーティンジェームス

3
また、「lockethなしで他のスレッドからもアクセスできる変数にはアクセスしないでください」-読み取った値が時々最新でないか、実際に著しく不正確である場合は問題になりません。
マーティンジェームス

ここで旋風にあなたを取るためにエリックによる素敵なブログがあります。
RBT 2017

回答:


139

メソッド(インスタンスまたは静的)がそのメソッド内でスコープされた変数のみを参照する場合、各スレッドには独自のスタックがあるため、スレッドセーフです。

この場合、複数のスレッドがThreadSafeMethod問題なく同時に呼び出すことができます。

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

これは、メソッドがローカルスコープの変数のみを参照する他のクラスメソッドを呼び出す場合にも当てはまります。

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

メソッドが(オブジェクト状態)プロパティまたはフィールド(インスタンスまたは静的)にアクセスする場合は、ロックを使用して、別のスレッドによって値が変更されないようにする必要があります。

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

メソッドに渡された、構造体でも不変でもないパラメーターは、メソッドのスコープ外の別のスレッドによって変更される可能性があることに注意してください。

適切な並行性を確保するには、ロックを使用する必要があります。

詳細については、lockステートメントのC#リファレンスReadWriterLockSlimを参照してください。

ロックは、一度に1つずつ機能を提供する場合に役立ち、
ReadWriterLockSlim複数のリーダーと単一のライターが必要な場合に役立ちます。


15
3番目の例private string someValue;ではstaticそうではないので、各インスタンスはその変数の個別のコピーを取得します。では、これがスレッドセーフではない理由を説明していただけますか?
Bharadwaj 14

29
@Bharadwaj- Thing複数のスレッドによってアクセスされるクラスのインスタンスが1つある場合
Trevor Pilley 14

111

メソッドがローカル変数にのみアクセスする場合、それはスレッドセーフです。それですか?

絶対にありません。スレッドセーフではないにもかかわらず、単一のスレッドからアクセスされる単一のローカル変数のみでプログラムを作成できます。

https://stackoverflow.com/a/8883117/88656

それは静的メソッドにも適用されますか?

絶対違う。

@Cybisによって提供された1つの答えは、「各スレッドが独自のスタックを取得するため、ローカル変数はスレッド間で共有できない」でした。

絶対違う。ローカル変数の特徴は、一時的なプールに割り当てられているのではなく、ローカルスコープ内からのみ表示されることです。2つの異なるスレッドから同じローカル変数にアクセスすることは完全に合法であり可能です。匿名メソッド、ラムダ、イテレータブロック、または非同期メソッドを使用して、これを行うことができます。

静的メソッドの場合もそうですか?

絶対違う。

メソッドに参照オブジェクトが渡された場合、それはスレッドの安全性を損ないますか?

多分。

私はいくつかの調査を行いましたが、特定のケースについてはたくさんありますが、いくつかのルールを使用するだけで、メソッドがスレッドセーフであることを確認するために従うガイドラインを定義できることを望んでいました。

あなたは失望とともに生きることを学ばなければならないでしょう。これは非常に難しいテーマです。

したがって、私の究極の質問は次のとおりだと思います。「スレッドセーフなメソッドを定義するルールの短いリストはありますか?

いいえ。前例で見たように、空のメソッドはスレッドセーフではない可能性があります。「メソッドが正しいことを保証するルールの短いリストはありますか?」いいえ、ありません。スレッドセーフティは、非常に複雑な種類の正当性にすぎません。

さらに、あなたが質問をしているという事実は、スレッドの安全性に関する根本的な誤解を示しています。スレッドセーフティはグローバルであり、プログラムのローカルプロパティではありません。正しく理解するのが非常に難しいのは、プログラムの安全性を確保するために、プログラム全体のスレッド動作について完全な知識が必要だからです

もう一度、私の例を見てください。すべてのメソッドは簡単です。これは、メソッドが「グローバル」レベルで相互に対話する方法であり、プログラムがデッドロックになります。あなたはすべての方法を見て「安全」としてチェックオフし、プログラム全体が安全であることを期待することはできません。あなたの家は100%非中空のレンガでできているので、家もそうです。非中空。家の空洞は全体のグローバルプロパティであり、パーツのプロパティの集合ではありません。


14
コアステートメント:スレッドセーフはグローバルであり、プログラムのローカルプロパティではありません。
Greg D

5
@BobHorn:class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } M()を呼び出してから、2つの異なるスレッドでC.getterとC.setterを呼び出します。ローカルであっても、2つの異なるスレッドでローカル変数を読み書きできるようになりました。繰り返しますが、ローカル変数の特徴は、それがローカルであることであり、スレッドのスタック上にあることではありません。
Eric Lippert、2012年

3
@BobHorn:権限と知識でツールについて話すことができるレベルでツールを理解することと関係があると思います。たとえば、元の質問は、ローカル変数が何であるかについての理解の欠如を裏切った。このエラーは非常に一般的ですが、実際にはエラーであり、修正する必要があります。ローカル変数の定義は非常に正確であり、それに応じて処理する必要があります。:)これは、病的なケースが存在することを理解していない「人々」のための答えではありません。これらは、実際に尋ねた内容を理解できるようにするための説明です。:)
グレッグD

7
@EricLippert:多くのクラスのMSDNドキュメントでは、この型の 'Public static(Visual BasicではShared)メンバーはスレッドセーフであることを宣言しています。インスタンスメンバーは、スレッドセーフであるとは限りません。または「このタイプはスレッドセーフです。」(文字列など)、スレッドの安全性が世界的な関心事である場合、これらの保証をどのように行うことができますか?
Mike Zboray

3
@mikez:いい質問ですね。これらのメソッドは状態を変更しないか、変更する場合はロックなしで状態を安全に変更するか、ロックを使用する場合はグローバルな「ロック順序」に違反しないことを保証する方法で変更します。文字列はメソッドが何も変更しない不変のデータ構造なので、文字列は簡単に安全にすることができます。
Eric Lippert

11

厳格な規則はありません。

.NETでコードスレッドを安全にするいくつかのルールと、これらが適切なルールではない理由を次に示します。

  1. 関数とそれが呼び出すすべての関数は、純粋で(副作用なし)、ローカル変数を使用する必要があります。これによりコードがスレッドセーフになりますが、.NETでこの制限を使用して実行できる興味深いことはほとんどありません。
  2. 共通のオブジェクトを操作するすべての関数lockは、共通のものである必要があります。すべてのロックは同じ順序で行う必要があります。これにより、コードスレッドは安全になりますが、非常に遅くなり、複数のスレッドを使用しないこともできます。
  3. ...

コードスレッドを安全にするルールはありません。実行できる唯一のことは、コードがアクティブに実行されている回数に関係なくコードが機能することを確認することです。各スレッドは、いつでも中断され、独自の状態/場所、およびこれは、共通オブジェクトにアクセスしている(静的またはその他の)関数ごとに。


10
ロックは遅い必要はありません。ロックは信じられないほど高速です。競合しないロックの大きさは、10〜100 ナノ秒です。もちろん、競合するロックは任意に遅いです。ロックの競合により速度が低下した場合は、プログラムを再設計して競合を解消してください。
Eric Lippert 2012年

2
#2を十分に明確にできなかったと思います。スレッドを安全にする方法があると言っていました。一般的なオブジェクトへのアクセスごとにロックをかけます。そして、複数のスレッドがあると仮定すると、競合が発生し、ロックによって全体の速度が低下します。ロックを追加するとき、盲目的にロックを追加することはできません。非常に優れた戦略的な場所に配置する必要があります。
EarlNameless

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.