2016年8月1日月曜日

[C#] Thread(スレッド)

こんにちは。明月です。
今日は「Thread」(スレッド)に関して勉強します。


Thread(スレッド)


プログラムを扱ってある方なら、特にアプリの関して開発したことがある方ならスレッドについて一回以上に聞いたことがあると思います。
スレッドを定義するとプログラムを実行する最小の単位として一つのプロセス(プログラム)の中で一つ以上のスレッドを動かすことができまして並列処理をするように作成する要素だと思います。


MSDNリンク - Thread(スレッド)


説明が難しいですが、例を通って続きます。



上の例を見ると我々は「ThreadEx(“Test1”)」、「ThreadEx(“Test2”)」、「ThreadEx(“Test3”)」の順に実行することはよく知っています。



でも、プログラムを作成する時にパフォーマンスとためにあるいは他の理由のために、上のメソッドを同時に実行する時があります。




上のソースを参照して結果を見ると「ThreadEx(“Test1”)」、「ThreadEx(“Test2”)」、「ThreadEx(“Test3”)」を順で呼び出しましたが、処理は順番通りではなく、同時に処理にするみたいにみえること、すなわち、並列に処理することで見えます。
スレッドを呼ばれた後に「StartWatchThread」を関数を呼ばれる部分があります。スレッド処理の中では「Program」のコンストラクタも一つのスレッドになるため、「ThreadEx(“Test1”)」、「ThreadEx(“Test2”)」、「ThreadEx(“Test3”)」をスレッド並列処理をすると各関数が終わる前に「Program」のコンストラクタが終わってしまう現象になるはずです。
そのため、「StartWatchThread」の関数は各スレッドが終わる時まで待つ処理、すなわち、メインスレッドがサブスレッドを待つ処理、改めていうと同期化処理を実装しています。


これからスレッドを使うことでプログラムのパフォーマンスがどのくらいに改善することができるかを確認しましょう。




上のソースを見るとスレッドを使わない処理時間(始めの例)とスレッドを使った処理時間(二つ目の例)を比較してみると約3倍に早くなることの結果が出ます。
スレッドを使うと必ずパフォーマンス改善になって処理速度がアップすることではないけれども、上の例はIOみたいに処理パフォーマンスと関係がある要素は明確に処理時間の差が見えます。


上の例をみるとスレッドを使うことがそんなに難しくなさそうだし、簡単に使えそうですね。でも実際プロジェクトする時にスレッドを簡単につかえられないし、みんなスレッドはプログラム処理の中で一番に難しいんだと言う方がいますが、その理由は同期化のせいです。


lock



上の例をみるとメンバ変数に「count」を割り当て「ThreadEx」のメソッドの中で「i」の値を増加しています。それで、我々が予想するデータ値は(0 ~ 99までの合計) * 3倍になりますね。(1 ~ 100まで合計は5050(101*50) – 100 = 4950で3倍は14850)



でも実際の結果を見ると値が「14850」ではないですね。もっと大変なことは実行するたびに値が一定値ではなく値が変わります。理由としては3つのスレッドが並列で流れているせいに発生することです。すなわち、「count」から取得値、または格納する時に3つスレッド達がお互いに影響されるためです。
改めていうと、「ThradEx(“Test1”)」のスレッドが実行する時に「count」を取得して「temp」に格納して再格納する前に「ThradEx(“Test2”)」で取得、「ThradEx(“Test1”)」で「count」にデータを1を増加して格納しますが、「ThradEx(“Test2”)」で「count = 0」から「1」を増加したデータを上書きしたせいです。そのために、3つのスレッドに「count」値にデータを増加したいと思うと値同期化が必要です。


そのために、3つのスレッドに「count」値にデータを増加したいと思うと値同期化が必要です。




「lock」のキーワードはオブジェクトタイプの対して(int、charタイプはlockを設定できない)「lock」が解けるまで待機するキーワードですが上の例とおりに「lock」を掛けると三つのスレッドで流れてもロックのことで順番通りに処理するので結果が「14850」になる形になります。


スレッド処理の中で「lock」まで勉強して同期化する方法まで勉強しましたが、スレッドが難しいというのはデッドロックのためです。


DeadLock


デッドロックは二つ以上の処理フローがお互いに終わることを待ち状況になることをデッドロックといいます。




上の例をみるとわざわざにデッドロックを掛けるように実装したので上の通りに実装するかたはいないと思いますが、デッドロックを説明するためにデッドロックが発生するように実装しました。
「ThreadEx」のメソッドをみると「threadList」のロックの下に「this」ロックがあり、次の処理は「this」のロックの下に「threadList」のロックがある状況に実装しました。
上のプログラムを実行するみるととデッドロックに落ちます(プログラムが動かない状況)。なぜなら「threadList → this」のロックは「this」のロックが解けるまでに待つ、「this → threadList」のロックは「threadList」のロックが解けるまでに待つ状況になるとお互いにロックが解除することを待っている状況になりまして、デッドロックになります。

それで、わざとデッドロックが発生するように実装する方はいないと思いますが、実装ミスかスレッド計算ミスでデッドロックを発生するケースが多いですね。
「デッドロックが発生する時に修正するといいじゃない」と簡単に思うことができますが、実際にデッドロックが発生するように実装すると一定的に発生することではなく、起きる時もあるし起きない時もあるし、ロックのブロックが大きくなればデッドロック発生部分を知らない時もあるし、流れは止まってないがリックが発生してサーバが落ちるケースもあるので、原因不明になって直すことが簡単にできない可能性があります。(サーバ落ちる問題なら、全ソースをレビューするときもあります。)

0 件のコメント