同期処理の基礎
同期処理
これまでタスクを使用してスレッドを起動する方法やメインスレッドとバックグラウンドスレッド間でデータを やり取りする方法を解説してきました。またローカル変数がどのようにしてスレッドごとに分離されるのかまた 参照型がどのようにしてスレッド間で共有されるのかも解説し、共有した変数に対しての処理も見てきました。
次は同期について解説します。同期を使うと複数のスレッドからの操作を調整し予測可能な結果を出すことが できます。同期処理は共有した同じデータに対して複数のスレッドからアクセスする場合に特に重要です。 同期を使用するとこういった問題に対して非常に簡単に対処できます。

同期には4つの種類があります。
単純なブロック用のメソッド
他のスレッドの完了を待つか指定した時間が経過するかするまで待つためのメソッドです。Sleep、Join、Task.Wait メソッドなどがこれにあたります。
ロック
いくつかのスレッドが同時に何かの処理を実行しているとします。排他ロックは非常に知られているロックです。 排他ロックを使用すると一度に1つだけのスレッドが処理を実行でき他のスレッドからの干渉を防ぐことが可能です。 最も標準的な排他ロックはlock(Monitor.Enter/Monitor.Exit)、Mutex、SpinLockです。排他ロックではないロック としてはSemaphore、SemaphoreSlim、Reader/Writer locksがあります。
シグナル
シグナルを使用すると他のスレッドから通知を受け取るまでスレッドを停止することができます。シグナルを使用 することで必要のない非効率なポーリングを避けることができます。シグナルとして2種類のシグナルがよく使用 されます。Event wait handleとMonitorのWait/Pulseメソッドです。.NET4.0ではCountdownEventやBarrierクラスが 新たに追加されてます。
ブロックしない同期処理
上記の機能はプロセッサが持っている排他処理を使って共有フィールドへのアクセスをブロックします。CLR及びC# は以下のプロセッサレベルではブロックしない同期処理を提供しています。Thread.MemoryBarrier、Thread.VolatileRead、 Thread.VolatileWrite、volatileキーワード、Interlockedクラスが提供されています。 ブロッキングは絶対不可欠な機能です。最後の章で解説しています。簡単に次の章で解説します。
ブロッキング
スレッドはその実行が何らかの理由で停止されている場合ブロックされていると見なされます。例えばSleepメソッドや 他のスレッドの完了をJoinやEndInvokeで待っている場合などです。ブロックされたスレッドはタイムスライスを直ちに OSに返却し、ブロックが解除されるまでCPU時間を一切消費しません。スレッドがブロックされているかどうかは ThreadStateプロパティで取得できます。
bool blocked = (someThread.ThreadState & ThreadState.WaitSleepJoin) != 0;
スレッドの状態はその値を読み取ったタイミングと次のタイミングでは異なる可能性があります。このプロパティは何か 不具合を診断したりなどしているときにのみ有用です。
スレッドがブロックされていようとされていまいとOSは常にコンテキストの切り替えを行っています。この切り替え処理 は約数マイクロ秒の時間がかかります。
ブロックの解除は以下の4つの方法のうちの1つによって発生します。 Suspendメソッド(廃止予定)を使用して停止されたスレッドはブロックされているとみなされません。
Blocking vs Spinning
ときどきある特定の条件を満たすまでスレッドを停止しておきたいときがあります。シグナルもしくはロックを使用すれば 非常に効率の良い方法でこのような用途を満たすことが可能です。しかしながらこれらの代替案としてもっと簡単な方法が あります。定期的にポーリングを行うループを作成してそのループの中で条件が満たされていないかをチェックする形です。
while (!proceed);
と記述するかあるいは
while (DateTime.Now < nextStartTime);
と記述すると可能です。一般的にこの方法はCPU時間を非常に無駄に使用します。CPU及びCLRから見るとスレッドは何かしら 意味のある計算をしていると見なされ、結果としてスレッドには様々なリソースを割り当てられているかれです。
しばしば以下のようなブロッキングとスピニングの両方を使用する記述が使われることもあります。
while (!proceed) Thread.Sleep (10);
あまりエレガントな方法ではないかもしれませんがこの方法は上記のスピニングよりははるかに無駄のない方法になります。 しかしながらproceed変数の変更に関しては並列処理による別の問題が発生します。これを避けるためにブロッキングか シグナルを適切に使用する必要があります。
スピニングは条件が非常に短い時間で(数マイクロ秒程度)満たされることがわかっている場合に有用です。なぜなら スレッドのコンテキストスイッチによる遅延を避けることが可能だからです。.NETはスピニングを実現するために いくつかの特別なメソッドを用意しています。これについては高度なスレッド処理 で解説しています。
ThreadState
スレッドの実行状態をThreadStateプロパティから取得することができます。このプロパティはThreadStateというEnum型で ほとんどの値は重複してたり使われていなかったり廃止予定だったりします。
下のコードはThreadStateのうち最も有用な4つのものを示しています。Unstarted,Running,WaitSleepJoin,Stopped
public static ThreadState SimpleThreadState (ThreadState ts)
{
  return ts & (ThreadState.Unstarted |
               ThreadState.WaitSleepJoin |
               ThreadState.Stopped);
}
ThreadStateプロパティはスレッドの状態を判断する場合には有効ですが、同期処理に使用するには不適切です。 なぜならばスレッドの状態を読みとったあとに処理を行うまでの間に状態が変わっている可能性があるからです。
ロック
Create at 2012/12/12 LastUpdate 2012/12/18