マルチスレッドの管理①

排他制御

スレッドセーフでないオブジェクトに対して複数スレッドからアクセスした場合、競合が発生します。
例えば、コレクションへの追加中に、他スレッドからは削除が行われた場合、追加結果が期待値と異なってしまうといった状況です。
追加処理に排他制御を掛けることで、こういった状況を回避できます。

lockステートメント

他スレッドからのアクセスを制限したい一連の処理をlockステートメント内に記述します。
lockステートメントには、ロック中かどうかを判別するためのobject(ロックオブジェクト)を指定します。

var lockObj = new object();
lock (lockObj)
{
     // 排他制御が必要な処理
}

動きは以下のようになります。

  1. スレッド①がlockステートメントに到達した場合、ロックオブジェクトがロックされます。
  2. スレッド②がlockステートメントに到達した場合、同様にロックオブジェクトのロックを試みますが、既にロック済みなので待機します。
  3. スレッド①がlockステートメントを抜けた段階でロックが開放され、スレッド②がロックして処理を再開します。

ポイントとして、ロックオブジェクトはそのlockステートメント専用のものを用意するのが好ましいです。
他でもロックオブジェクトとして使用されるような場合、デッドロックが発生する可能性があります。

Monitor

Monitorクラスを使用することで、lockと同様に排他処理を実現できます。
(lockステートメントコンパイルされるとtry/catch+Monitorを使用したコードに展開される。)
Monitor.Enter()~Monitor.Exit()の間に処理を記述します。

try
{
    Monitor.Enter(array);
    // 排他制御が必要な処理
}
finally
{
    Monitor.Exit(array);
}

Monitorはロック開放のために確実にMonitor.Exit()を呼び出す必要があります。
例外が発生した場合を考慮し、try/finallyで実装するのが安全です。
また、ロックに成功したかどうかを返すMonitor.TryEnter()メソッドなど、より柔軟な排他実装を行うためのメソッドが用意されています。
参考 : Monitor Class (System.Threading) | Microsoft Docs

Interlocked

Interlockedクラスは変数に対する排他的な処理を提供します。

Interlocked.Add(ref hoge, 5); // 変数hogeに対して5を加算する処理を排他的に行います
Inerlocked.Increment(ref hoge); // 変数hogeに対してインクリメントする処理を排他的に行います

例としてあげた上記コードの場合、メモリからの読み出し・計算・書き戻しの処理に排他を掛けるため、その間に他スレッドからの割り込みは発生しません。
参考 : Interlocked Class (System.Threading) | Microsoft Docs