Race ConditionとInterlocked


正式に説明する前に、次のコードをチェックします.
class Program
    {

        static int number = 0; 

        static void Thread_1()
        {
            for(int i=0; i<100000; i++)
            {
                number++; 
            }
        }

        static void Thread_2()
        {
            for(int i=0; i<100000; i++)
            {
                number--; 
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(number);
        }
   }
numberプラス1の演算を10万回、numberプラス1の演算を10万回行う.
もちろん数はゼロに割り当てられるべきだと思います.

しかし、結果はまったく予想外の結果になった.
このような現象の原因は競争条件である.
Race Conditionとは?
マルチスレッド環境では、2つの命令が同じメモリに同時にアクセスすると、それらの競合により実行結果が予知できなくなります.
あなたがもっと理解しやすいように、レストランの角度から説明します.

レストランからコーラを注文されると、従業員たちは競争します.

客(利用者)に複数のコーラをもたらす現象と理解すれば容易である.
つまり,スレッド間の競合により実行結果を予測することができない.
コードをよく見てみましょう.
number++;

number--; 
2つのコードは、コンポーネント言語の観点から以下のように説明できます.
int temp = number; 

temp += 1; 

number = temp; 


int temp = number; 

temp -= 1;

number = temp; 
number++とnumber--コードは、3つの演算で動作するコードです.
したがって,マルチスレッド環境では,上記6行のコードが同時に実行され,結果の予測が不可能になる.
これらの問題を解決するには、原子性を保証すればいい.
原子性とは何ですか.再分割不可能な性質で、実行時に常に完全に行われて終了したり、実行不可能な場合に実行しない場合を指す.
すなわち,number++とnumber--演算は原子性を保証し,一度に実行したり,いっそ実行したりすることを避けることができる.
修正コードは次のとおりです.
static void Thread_1()
{
	for(int i=0; i<100000; i++)
    	{
    		Interlocked.Increment(ref number);
    	}
}

static void Thread_2()
{
	for(int i=0; i<100000; i++)
	{
    		Interlocked.Decrement(ref number); 
	}
}
ロックシリーズはAllor Nothingです.すなわち、実行または実行しない.
1つのロックが実行されると、もう1つのロックが待機されます.
このように、多発的な競争を同時に行えば、最終的な勝者だけが仕事を行い、結果が保障される.
しかし、仕事のスピードはもちろん元の演算より遅い.1つのジョブが終わるまで、他のジョブは待たなければならないからです.
番外、numberという値を抽出したい場合は、どうすればいいですか?
for(int i=0; i<100000; i++)
{
	prev = number; 
	Interlocked.Increment(ref number);
	next = number; 
}
そうするのはもちろんだめだ.
prev=numberのようにnumber(Load)を抽出する演算では他のスレッドからnumberにアクセスできるため予測不可能な結果が生じる.
int afterValue = Interlocked.Increment(ref number);
上記のコードに示すように,反発関数の戻り値からnumberの値を抽出しなければならない.