web-dev-qa-db-ja.com

このC#メソッドがスレッドセーフかどうかを確認するにはどうすればよいですか?

ASP.NETキャッシュアイテム削除イベントのコールバック関数の作成に取り組んでいます。

ドキュメントには、静的メソッドなど、オブジェクトのメソッドまたは存在することがわかっている(スコープ内にある)呼び出しを呼び出す必要があると記載されていますが、静的がスレッドセーフであることを確認する必要があると記載されています。

パート1:スレッドセーフでなくするために私ができることの例は何ですか?

パート2:これは、

static int addOne(int someNumber){
    int foo = someNumber;
    return foo +1; 
}

class.addOne(5)を呼び出します。およびClass.addOne(6);同時に、誰が最初にfooを設定するかに応じて、6または7が返されることがありますか? (つまり、競合状態)

49
MatthewMartin

そのaddOne関数は、別のスレッドがアクセスできるデータにはアクセスしないため、確かにスレッドセーフです。各スレッドは独自のスタックを取得するため、ローカル変数はスレッド間で共有できません。ただし、関数パラメーターが値型であり、参照型ではないことを確認する必要があります。

static void MyFunction(int x) { ... } // thread safe. The int is copied onto the local stack.

static void MyFunction(Object o) { ... } // Not thread safe. Since o is a reference type, it might be shared among multiple threads. 
52
Cybis

いいえ、ここではaddOneはスレッドセーフです-ローカル変数のみを使用します。 しないがスレッドセーフになる例を次に示します。

 class BadCounter
 {
       private static int counter;

       public static int Increment()
       {
             int temp = counter;
             temp++;
             counter = temp;
             return counter;
       }
 }

ここでは、2つのスレッドが両方とも同時にIncrementを呼び出すことができ、最終的には1回だけインクリメントします。 (return ++counter;ちなみに、上記と同じように、より明示的なバージョンです。私はそれを拡大したので、もっと明らかに間違っているでしょう。)

スレッドセーフであるかどうかの詳細は非常に扱いにくい場合がありますが、一般的には、(とにかく渡されたもの以外に)状態を変更していない場合は、それは- 通常大丈夫。

38
Jon Skeet

スレッド化の問題(最近私も心配しています)は、個別のキャッシュを備えた複数のプロセッサコアを使用していることと、基本的なスレッドスワッピングの競合状態から発生しています。別々のコアのキャッシュが同じメモリの場所にアクセスする場合、それらは通常、他のコアを認識せず、メインのメモリ(または、すべてで共有されている同期キャッシュ)に戻ることなく、そのデータの場所の状態を個別に追跡できます。 L2またはL3のコアなど)、プロセッサのパフォーマンス上の理由から。したがって、マルチスレッド環境では、実行順序のインターロックトリックでさえ信頼できない場合があります。

ご存知かもしれませんが、これを修正するための主なツールはロックです。これは、排他的アクセス(同じロックの競合間)のメカニズムを提供し、基になるキャッシュ同期を処理して、さまざまなロック保護された同じメモリ位置へのアクセスを可能にします。コードセクションは適切にシリアル化されます。誰がいつどのような順序でロックを取得するかの間で競合状態が発生する可能性はありますが、ロックされたセクションの実行が(そのロックのコンテキスト内で)アトミックであることを保証できる場合は、通常、処理がはるかに簡単です。

任意の参照型のインスタンスでロックを取得できます(たとえば、intやenumのような値型ではなく、nullではないObjectから継承します)が、オブジェクトのロックはアクセスに固有の影響を与えないことを理解することは非常に重要ですそのオブジェクトに対して、それは同じオブジェクトのロックを取得する他の試みとのみ相互作用します。適切なロックスキームを使用してメンバー変数へのアクセスを保護するのは、クラスです。場合によっては、インスタンスが自身をロックすることで自身のメンバーへのマルチスレッドアクセスを保護することがあります(例:lock (this) { ... })。ただし、インスタンスは1人の所有者だけが保持する傾向があるため、通常、これは必要ありません。インスタンスへのスレッドセーフアクセスを保証します。

より一般的には、クラスはプライベートロックを作成します(たとえば、各インスタンス内の個別のロックに対してprivate readonly object m_Lock = new Object();を作成して、そのインスタンスのメンバーへのアクセスを保護します。または、中央のロックに対してprivate static readonly object s_Lock = new Object();を使用してアクセスを保護します。クラスの静的メンバー)。 Joshには、ロックを使用するより具体的なコード例があります。次に、ロックを適切に使用するようにクラスをコーディングする必要があります。より複雑なケースでは、メンバーのグループごとに別々のロックを作成して、一緒に使用されないさまざまな種類のリソースの競合を減らすこともできます。

したがって、元の質問に戻すために、独自のローカル変数とパラメーターにのみアクセスするメソッドは、スレッドセーフになります。これらは、現在のスレッドに固有のスタック上の独自のメモリロケーションに存在し、アクセスできないためです。他の場所-それらを渡す前にスレッド間でそれらのパラメーターインスタンスを共有しない限り。

インスタンス所有者のメンバー(静的メンバーではない)にのみアクセスする非静的メソッド(そしてもちろんパラメーターとローカル変数)は、単一の所有者によって使用されているインスタンスのコンテキストでロックを使用する必要はありません(そうではありません)スレッドセーフである必要があります)が、インスタンスが共有され、スレッドセーフなアクセスを保証したい場合、インスタンスは、そのインスタンスに固有の1つ以上のロック(メンバーのロック)でメンバー変数へのアクセスを保護する必要があります。インスタンス自体は1つのオプションです)-スレッドセーフな共有を意図していないものを共有する場合、インスタンスの周りに独自のロックを実装するのは呼び出し側に任せるのではありません。

操作されていない読み取り専用メンバー(静的または非静的)へのアクセスは一般に安全ですが、それが保持するインスタンス自体がスレッドセーフではない場合、または複数の操作にわたって原子性を保証する必要がある場合は、独自のロックスキームを使用して、すべてのアクセスを保護します。これは、インスタンスがそれ自体でロックを使用している場合に便利な場合があります。原子性のためにインスタンスへの複数のアクセスにわたってインスタンスのロックを取得するだけでよいのですが、それが単一アクセスの場合はそうする必要はありません。自身をロックして、それらのアクセスを個別にスレッドセーフにします。 (それが自分のクラスでない場合は、それ自体がロックしているか、外部からアクセスできないプライベートロックを使用しているかを知る必要があります。)

そして最後に、インスタンス内から(指定されたメソッドまたは他のメソッドによって変更された)静的メンバーの変更にアクセスできます。もちろん、これらの静的メンバーにアクセスし、だれからでも、どこからでも、いつでも呼び出せる静的メソッドがあります。責任あるロックを使用する最大の必要性。これがないと、スレッドセーフではなく、予期しないバグが発生する可能性があります。

.NETフレームワーククラスを処理する場合、MicrosoftはMSDNで、特定のAPI呼び出しがスレッドセーフであるかどうかをドキュメント化します(例:_List<T>_などの提供されたジェネリックコレクションタイプの静的メソッドはスレッドセーフになりますが、インスタンスメソッドはそうではありません- -ただし、具体的に確認してください)。ほとんどの場合(具体的にはスレッドセーフであると明記されていない限り)、内部でスレッドセーフではないため、安全な方法で使用する必要があります。また、個々の操作が内部でスレッドセーフに実装されている場合でも、アトミックである必要があるより複雑な処理を行う場合は、コードによる共有アクセスと重複アクセスを心配する必要があります。

1つの大きな注意点は、コレクションを反復処理することです(例:foreach)。コレクションへの各アクセスが安定した状態になったとしても、それらのアクセス間で変更が発生しないという固有の保証はありません(他の場所にアクセスできる場合)。コレクションがローカルに保持されている場合、通常は問題はありませんが、(別のスレッドによって、またはループの実行中に)変更される可能性のあるコレクションは、一貫性のない結果を生成する可能性があります。これを解決する簡単な方法の1つは、アトミックスレッドセーフ操作(保護ロックスキーム内)を使用してコレクション(MyType[] mySnapshot = myCollection.ToArray();)の一時的なコピーを作成し、ロック外のローカルスナップショットコピーを反復処理することです。 。多くの場合、これはずっとロックを保持する必要性を回避しますが、イテレーション内で何をしているかに応じて、これは十分ではないかもしれません、そしてあなたはただずっと変更から保護しなければなりません(またはあなたはすでにそれを持っているかもしれません)他のものと一緒にコレクションを変更するためのアクセスを防ぐロックされたセクションなので、カバーされます)。

したがって、スレッドセーフな設計には多少の芸術があり、ロックを取得して保護する場所と方法を知ることは、クラスの全体的な設計と使用法に大きく依存します。偏執狂に陥りやすく、すべてにロックをかける必要があると考えるのは簡単ですが、実際には、物事を保護する適切なレイヤーを見つけることが重要です。

22
Rob Parker

ローカル変数のみを使用しているため、メソッドは問題ありません。メソッドを少し変更してみましょう。

static int foo;

static int addOne(int someNumber)
{
  foo=someNumber; 
  return foo++;
}

静的データを操作しているため、これはスレッドセーフな方法ではありません。次に、次のように変更する必要があります。

static int foo;
static object addOneLocker=new object();
static int addOne(int someNumber)
{
  int myCalc;
  lock(addOneLocker)
  {
     foo=someNumber; 
     myCalc= foo++;
  }
  return myCalc;
}

これは私がちょうどそれを正しく読んでいる場合に引き起こしたばかげたサンプルだと思いますが、もうfooには意味がありませんが、ちょっとそれはサンプルです。

7
JoshBerke

これは、関数の外部にある変数を変更する場合にのみ、競合状態になります。あなたの例はそれをしていません。

それは基本的にあなたが探しているものです。スレッドセーフとは、関数が次のいずれかであることを意味します。

  1. 外部データを変更しない、または
  2. 外部データへのアクセスは適切に同期されているため、一度に1つの関数のみがアクセスできます。

外部データは、ストレージ(データベース/ファイル)に保持されているものでも、アプリケーションの内部(変数、クラスのインスタンスなど)でもかまいません。基本的に、関数のスコープ外にある世界のどこかに宣言されているものです。

関数のスレッドセーフでないバージョンの簡単な例は次のとおりです。

private int myVar = 0;

private void addOne(int someNumber)
{
   myVar += someNumber;
}

同期せずに2つの異なるスレッドからこれを呼び出す場合、myVarの値のクエリは、addOneへのすべての呼び出しが完了した後にクエリが発生するか、2つの呼び出しの間にクエリが発生するか、どちらかの前にクエリが発生するかによって異なります。呼び出し。

3
TheSmurf

スレッドセーフではないコードを検出できるようにするためにいくつかの調査が行われています。例えば。プロジェクト CHESS at Microsoft Research

3
Thomas Danecker

上記の例では、.

スレッドの安全性は主に、保存された状態に関係しています。これを行うと、上記の例をスレッドセーフでなくすることができます。

static int myInt;

static int addOne(int someNumber){
myInt = someNumber;
return myInt +1; 
}

これは、コンテキストの切り替えにより、スレッド1がmyInt = someNumberを呼び出してからコンテキストを切り替える可能性があるため、スレッド1が単に5に設定するとします。次に、スレッド2が入り、6を使用して7を返すことを想像してください。 1は再びウェイクアップし、myIntで使用していた5ではなく6を持ち、予期された6ではなく7を返します。:O

2
Quibblesome

どこでも スレッドセーフ は、リソースにアクセスしているときに2つ以上のスレッドが衝突しないことを意味します。通常、静的変数--- C#、VB.NET、Java ---などの言語では、コードがスレッドで安全ではない

Javaにはsynchronizedキーワードが存在しますが、.NETではアセンブリオプション/ディレクティブを取得します:


class Foo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Bar(object obj)
    {
        // do something...
    }
}

スレッドセーフでないクラスの例は、このパターンのコーディング方法に応じて、singletonsになります。通常、synchronizedインスタンス作成者を実装する必要があります。

同期メソッドが必要ない場合は、spin-lock

1
daniel

2つのスレッドで同時に使用できるオブジェクトへのアクセスは、スレッドセーフではありません。

パート2の例は、引数として渡された値のみを使用するため、明らかに安全ですが、オブジェクトスコープの変数を使用した場合、適切なロックステートメントでアクセスを囲む必要がある場合があります。

0
Oskar

fooは同時または順次の呼び出し間で共有されないため、addOneはスレッドセーフです。

0
yfeldblum

あなたの例で 'foo'と 'someNumber'が安全である理由は、それらがスタックに存在し、各スレッドが独自のスタックを持っているため共有されないためです。

データが共有される可能性がある場合(例えば、グローバルであったり、オブジェクトへのポインターを共有したり)になるとすぐに、競合が発生し、何らかのロックを使用する必要が生じる可能性があります。

0
quamrana