15

複数のクラスが使用する 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;
    }
}
4

9 に答える 9

30

'Instance'を介した'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ビット整数またはfloat(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は値型であるため、ロックすることはできません。
これを解決するには、「ダミー」オブジェクトを導入し、 「値」をロックたい場所でそれをロックします。

これはBenScheirmanが言及しているものです。

于 2008-09-03T20:49:28.723 に答える
8

ベンが指摘するように、元の実装はスレッドセーフではありません

スレッドセーフにする簡単な方法は、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;
        }
    }
}
于 2008-09-03T20:40:00.540 に答える
2

それが私が思ったことです。しかし、私は詳細を探しています...'Toggle()'は静的メソッドではありませんが、静的プロパティのメンバーです('インスタンス'を使用する場合)。それがスレッド間で共有される理由ですか?

アプリケーションがマルチスレッドであり、複数のスレッドがそのメソッドにアクセスすることを予測できる場合は、スレッド間で共有されます。クラスはシングルトンであるため、異なるスレッドが同じオブジェクトにアクセスすることがわかっているため、メソッドのスレッドセーフに注意してください。

そして、これは一般的にシングルトンにどのように適用されますか。クラスのすべてのメソッドでこれに対処する必要がありますか?

上で述べたように、シングルトンであるため、異なるスレッドが同じオブジェクトにアクセスする可能性があります。これは、すべてのメソッドにロックを取得させる必要があるという意味ではありません。同時呼び出しによってクラスの状態が破損する可能性があることに気付いた場合は、@Thomasで言及されているメソッドを適用する必要があります。

于 2008-09-03T20:58:08.860 に答える
2

シングルトンパターンが、他の点では素敵なスレッドセーフクラスを通常の静的メンバーのすべてのスレッド問題にさらしていると想定できますか?

いいえ。あなたのクラスは単にスレッドセーフではありません。シングルトンはそれとは何の関係もありません。

(静的オブジェクトで呼び出されたインスタンスメンバーがスレッド化の問題を引き起こすという事実に頭を悩ませています)

それもそれとは何の関係もありません。

あなたはこのように考える必要があります:私のプログラムで2つ(またはそれ以上)のスレッドが同時にこのデータにアクセスすることは可能ですか?

シングルトンまたは静的変数を介してデータを取得するという事実、またはメソッドパラメータとしてオブジェクトを渡すという事実は重要ではありません。結局のところ、それはすべてPCのRAM内のほんの一部のビットとバイトであり、重要なのは、複数のスレッドが同じビットを認識できるかどうかです。

于 2008-09-03T21:03:02.967 に答える
2

スレッドがそのメソッドの途中で停止し、制御を別のスレッドに移す可能性があります。そのコードの周りにクリティカルセクションが必要です...

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}
于 2008-09-03T20:32:55.020 に答える
1

また、保護されたコンストラクターをMyClassに追加して、コンパイラーがパブリックのデフォルトコンストラクターを生成しないようにします。

于 2008-09-03T20:41:41.130 に答える
1

シングルトンパターンをダンプして、クラスの新しいインスタンスを全員に取得させると、いくつかの問題が緩和されると思っていました...しかし、それでも、他の誰かがそのタイプの静的オブジェクトを初期化して渡すのを止めることはできません。 ..または複数のスレッドをスピンオフして、すべて同じインスタンスから「Toggle()」にアクセスします。

ビンゴ:-)

わかった。厳しい世界です。レガシーコードをリファクタリングしていなかったらよかったのに:(

残念ながら、マルチスレッド化は難しく、物事について非常に慎重にする必要があります:-)この場合の最も簡単な解決策は、例のように、シングルトンに固執し、値の周りにロックを追加することです。

于 2008-09-03T21:12:48.327 に答える
0

見積もり:

if(value == 0) { value = 1; }
if(value == 1) { value = 0; }
return value;

value常に0になります...

于 2008-09-03T20:32:20.280 に答える
0

ええと、私は実際にはC#をよく知りません...しかし、私はJavaで大丈夫なので、その答えを出します.うまくいけば、2つは十分に似ているので便利です. そうでない場合は、お詫び申し上げます。

答えは、いいえ、安全ではありません。1 つのスレッドが他のスレッドと同時に Toggle() を呼び出す可能性があり、このコードではほとんどありませんが、valueThread2 がチェックしてから設定するまでの間に Thread1 が設定される可能性があります。

修正するには、単純に Toggle() を作成しsynchronizedます。Toggle() を呼び出す可能性のある別のスレッドを生成する可能性のあるものをブロックしたり、呼び出したりすることはないため、保存する必要があるのはそれだけです。

于 2008-09-03T20:36:47.273 に答える