今回はC#の排他制御について解説します。
排他制御とは?
排他制御は、マルチスレッドやマルチプロセス環境で共有リソースへのアクセスを管理するための重要な動作です。
排他制御の目的は、競合状態を防ぎ、データ整合性やプログラムの安全性を確保することです。
競合状態は、複数のスレッドやプロセスが同時に共有リソースにアクセスし、予測不能な結果やエラーが発生する可能性がある状態を指します。
排他制御を実装することで、競合状態を防ぎ、正確で安全な動作を確保できます。
排他制御のサンプルコード
次にサンプルコードを提示していきます。
排他制御を使わない場合
まずは、排他制御を使わなかった例です。
サンプルコード
using System;
using System.Threading;
class Program
{
static int sharedCounter = 0;
static int maxCounter = 100;
static void Main()
{
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("最終的なカウンターの値: " + sharedCounter);
Console.WriteLine("本来想定していた値: " + maxCounter * 2);
}
static void IncrementCounter()
{
for (int i = 0; i < maxCounter; i++)
{
Thread.Sleep(50);
sharedCounter++; // 共有変数への非同期アクセス
}
}
}
実行結果
最終的なカウンターの値: 196
本来想定していた値: 200
このコードでは、2つのスレッドが同時に sharedCounter
変数をインクリメントしようとしています。排他制御がないため、競合状態が発生し、最終的なカウンターの値は予測不能です。
毎回必ずズレてくれればいいですが、システムによっては10万回に1回しか発生しない現象であったりすることがあり、かなり厄介な問題になりやすいです。
具体的には、先ほどのコードのThread.Sleep(50);
の処理を無くすと、最終的なカウンターが本来想定していた200回となり、10万回に1回だけ199回と出力されてしまうイメージです。
排他制御を使う場合
次に排他制御を使うとこのようになります。
サンプルコード
using System;
using System.Threading;
class Program
{
static int sharedCounter = 0;
static int maxCounter = 100;
static object lockObject = new object();
private static void Main(string[] args)
{
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("最終的なカウンターの値: " + sharedCounter);
Console.WriteLine("本来想定していた値: " + maxCounter * 2);
}
static void IncrementCounter()
{
for (int i = 0; i < maxCounter; i++)
{
lock (lockObject) // 排他制御用のロック
{
Thread.Sleep(50);
sharedCounter++; // 共有変数への同期アクセス
}
}
}
}
実行結果
最終的なカウンターの値: 200
本来想定していた値: 200
このコードでは、排他制御を実現するために lock
キーワードを使用しています。lock
ブロック内では、複数のスレッドが同時にアクセスできないようになります。したがって、競合状態を防ぎ、最終的なカウンターの値が正確になります。
排他制御を実装することで、複数のスレッドやプロセスがデータに安全にアクセスできるようになり、プログラムの信頼性と安定性が向上します。
コメント