シングルトンのメンバーのスレッドセーフな使用
-
09-06-2019 - |
質問
複数のクラスが使用する C# シングルトン クラスがあります。経由でアクセスしていますか Instance
に Toggle()
メソッドはスレッドセーフですか?「はい」の場合、どのような仮定、ルールなどによるのか。「いいえ」の場合、その理由 そして どうすれば直せますか?
public class MyClass
{
private static readonly MyClass instance = new MyClass();
public static MyClass Instance
{
get { return instance; }
}
private int value = 0;
public int Toggle()
{
if(value == 0)
{
value = 1;
}
else if(value == 1)
{
value = 0;
}
return value;
}
}
解決
「インスタンス」を介した「Toggle()」クラスへのアクセスはスレッドセーフですか?「はい」の場合、どのような仮定、ルールなどによるのか。いいえの場合、なぜ、どのように修正すればよいですか?
いいえ、スレッドセーフではありません。
基本的に、両方のスレッドで実行できます。 Toggle
同時に機能するため、このようなことが起こる可能性があります
// thread 1 is running this code
if(value == 0)
{
value = 1;
// RIGHT NOW, thread 2 steps in.
// It sees value as 1, so runs the other branch, and changes it to 0
// This causes your method to return 0 even though you actually want 1
}
else if(value == 1)
{
value = 0;
}
return value;
以下の前提で運用する必要があります。
2 つのスレッドが実行されている場合、それらは任意の時点でランダムにインターリーブして相互作用することができます。64 ビット整数または浮動小数点 (32 ビット CPU 上) の書き込みまたは読み取りの途中で、別のスレッドがジャンプして、下からそれを変更する可能性があります。
2 つのスレッドが共通のものにまったくアクセスしない場合は問題ありませんが、アクセスした場合はすぐに、それらが互いに足を踏み外さないようにする必要があります。.NET でこれを行うには、ロックを使用します。
次のようなことを考慮して、何をどこにロックするかを決定できます。
特定のコード ブロックについて、次の値がある場合、 something
私の下から着替えたのですが、問題ありますか?そうなった場合は、ロックする必要があります something
重要なコードの継続期間中。
あなたの例をもう一度見てみましょう
// we read value here
if(value == 0)
{
value = 1;
}
else if(value == 1)
{
value = 0;
}
// and we return it here
return value;
これが期待どおりの結果を返すには、次のように仮定します。 value
読み取りと読み取りの間で変更されません return
. 。この仮定が実際に正しいためには、ロックする必要があります。 value
そのコードブロックの期間中。
したがって、次のようにします。
lock( value )
{
if(value == 0)
... // all your code here
return value;
}
しかし
.NET では、参照型のみをロックできます。Int32 は値型であるため、ロックできません。
「ダミー」オブジェクトを導入し、ロックすることでこれを解決します。 それ 「値」をロックしたい場所ならどこでも。
これは何 ベン・シャイヤーマン を指しています。
他のヒント
Ben が指摘しているように、元の実装はスレッドセーフではありません
スレッドセーフにする簡単な方法は、lock ステートメントを導入することです。例えば。このような:
public class MyClass
{
private Object thisLock = new Object();
private static readonly MyClass instance = new MyClass();
public static MyClass Instance
{
get { return instance; }
}
private Int32 value = 0;
public Int32 Toggle()
{
lock(thisLock)
{
if(value == 0)
{
value = 1;
}
else if(value == 1)
{
value = 0;
}
return value;
}
}
}
そう思いました。しかし、私は詳細を探しています...「Toggle()」は静的な方法ではありませんが、静的プロパティのメンバーです(「インスタンス」を使用する場合)。それがスレッド間でそれを共有しているのはなぜですか?
アプリケーションがマルチスレッドであり、複数のスレッドがそのメソッドにアクセスすることが予測できる場合、そのメソッドはスレッド間で共有されます。クラスはシングルトンであるため、異なるスレッドが同じオブジェクトにアクセスすることがわかっているため、メソッドのスレッドセーフについて注意してください。
そして、これは一般的にシングルトンにどのように適用されますか。クラスのすべての方法でこれに対処する必要がありますか?
上で述べたように、これはシングルトンであるため、異なるスレッドが同じオブジェクトに、場合によっては同時にアクセスすることがわかります。これは、すべてのメソッドでロックを取得する必要があるという意味ではありません。同時呼び出しがクラスの破損状態を引き起こす可能性があることに気付いた場合は、@Thomas が言及したメソッドを適用する必要があります。
シングルトン パターンにより、本来は素晴らしいスレッドセーフなクラスが、通常の静的メンバーのすべてのスレッド問題にさらされると考えてもよいでしょうか?
いいえ。あなたのクラスは単純にスレッドセーフではありません。シングルトンはそれとは何の関係もありません。
(静的オブジェクトで呼び出されたインスタンスのメンバーがスレッドの問題を引き起こすという事実について理解しています)
それも関係ないんです。
次のように考える必要があります。私のプログラムでは、2 つ (またはそれ以上) のスレッドがこのデータに同時にアクセスすることは可能ですか?
シングルトンまたは静的変数を介してデータを取得するか、メソッドのパラメーターとしてオブジェクトを渡すかどうかは問題ではありません。結局のところ、それはすべて PC の RAM 内のいくつかのビットとバイトにすぎず、重要なのは複数のスレッドが同じビットを認識できるかどうかだけです。
スレッドがメソッドの途中で停止し、別のスレッドに制御が移る可能性があります。そのコードの周りにクリティカルセクションが必要です...
private static object _lockDummy = new object();
...
lock(_lockDummy)
{
//do stuff
}
また、コンパイラがパブリックなデフォルト コンストラクターを生成しないように、保護されたコンストラクターを MyClass に追加します。
シングルトン パターンをダンプして、全員にクラスの新しいインスタンスを強制的に取得させれば、いくつかの問題が軽減されるだろうと考えていました...しかし、だからといって、他の人がその型の静的オブジェクトを初期化し、それを渡すことを止めることはできません...または、複数のスレッドをスピンオフし、すべてが同じインスタンスから 'Toggle()' にアクセスします。
ビンゴ:-)
わかった。厳しい世界だ。レガシーコードをリファクタリングしなければよかったのに :(
残念ながら、マルチスレッドは困難であり、物事について非常に妄想的でなければなりません:-)この場合の最も簡単な解決策は、シングルトンに固執し、例のように値の周りにロックを追加することです。
うーん、実はC#はそこまで詳しくないんですが…しかし、私は Java が得意なので、それに対する答えを提供します。うまくいけば、この 2 つは十分に似ているので、役立つと思います。そうでない場合は、お詫び申し上げます。
答えは、いいえ、安全ではありません。一方のスレッドが他方のスレッドと同時に Toggle() を呼び出す可能性があり、このコードでは可能性は低いですが、Thread1 が value
Thread2 がそれをチェックしてから設定するまでの間に。
修正するには、Toggle() を作成するだけです。 synchronized
. 。これは何もブロックしたり、Toggle() を呼び出す可能性のある別のスレッドを生成する可能性のあるものを呼び出したりすることはないため、保存する必要があるのはそれだけです。
引用:
if(value == 0) { value = 1; }
if(value == 1) { value = 0; }
return value;
value
常に 0 になります...