データ競合とは
データ競合(Data Race)とは,複数のスレッドやプロセスが同時に共有されたデータにアクセスし,かつ少なくとも一つのアクセスが書き込みである場合に発生する問題です.データ競合が発生すると,不正確な結果やアプリケーションのクラッシュなどの予測不可能な挙動が発生する可能性があります.C#では,マルチスレッドプログラミングや非同期処理を行う際に特に注意が必要です.
具体的な場面
1. 銀行口座残高の競合:
複数のスレッドが同時に同じ銀行口座の残高を取得し,それぞれが残高を変更する場合.競合が発生すると,正確な残高が把握できなくなります.
2.カウンターの競合:
複数のスレッドが同時に共有のカウンターをインクリメントする場合.競合が発生すると,正確な回数が数えられなくなります。
3. データベースの同時更新:
複数のスレッドが同時にデータベース内の同じレコードを更新しようとする場合.競合が発生すると,データが破損する可能性があります.
再現コード
本投稿では,共有変数に対する同時のインクリメント操作を行います.
// 外部の名前空間をインポート
using System;
using System.Threading;
// C#プログラムは通常1つ以上のクラスから構成される
class DataRaceExample
{
// 共有変数として使用するためstatic宣言
// 本課題では複数スレッドからのアクセスを想定
static int sharedVariable = 0;
// インスタンス作成
// 共有変数と操作をインスタンスで共有するためstatic宣言
static void IncrementSharedVariable()
{
// 100万回インクリメント
for (int i = 0; i < 1000000; i++)
{
sharedVariable++;
}
}
// インスタンスを作成せずに実行させるためstatic宣言
static void Main()
{
// 2つのスレッドを作成
// 同じメソッドIncrementSharedVariableを実行
Thread thread1 = new Thread(IncrementSharedVariable);
Thread thread2 = new Thread(IncrementSharedVariable);
// メソッドIncrementSharedVariableを同時に実行
thread1.Start();
thread2.Start();
// Mainメソッドの終了防止のためthread1とthread2の実行が終了するまで待機
thread1.Join();
thread2.Join();
// 最終的なsharedVariableの値を表示(予測できない値になっている)
Console.WriteLine("Final shared variable value: " + sharedVariable);
// ウィンドウが閉じて値が見られなくなるのを防ぐため.文字入力まで待機
Console.ReadKey();
}
}
再現コードの結果
値が1166963や1073519などとなり,予測不能な値が出力されました.
修正コード
再現コードと同様,共有変数に対する同時のインクリメント操作を行います.具体的には,lockステートメントを使用することで競合を防ぐ処理を追加しました.
// 外部の名前空間をインポート
using System;
using System.Threading;
// C#プログラムは通常1つ以上のクラスから構成される
class DataRaceExample
{
// 共有変数として使用するためstatic宣言
// 本課題では複数スレッドからのアクセスを想定
static int sharedVariable = 0;
// 共有オブジェクトとして使用するためstatic宣言
// 本オブジェクトはロックのために使用
static object lockObject = new object();
// インスタンス作成
// 共有変数と操作をインスタンスで共有するためstatic宣言
static void IncrementSharedVariable()
{
// 100万回インクリメント
for (int i = 0; i < 1000000; i++)
{
// lockステートメントより同時アクセスを制御
lock (lockObject)
{
sharedVariable++;
}
}
}
// インスタンスを作成せずに実行させるためstatic宣言
static void Main()
{
// 2つのスレッドを作成
// 同じメソッドIncrementSharedVariableを実行
Thread thread1 = new Thread(IncrementSharedVariable);
Thread thread2 = new Thread(IncrementSharedVariable);
// メソッドIncrementSharedVariableを同時に実行
thread1.Start();
thread2.Start();
// Mainメソッドの終了防止のためthread1とthread2の実行が終了するまで待機
thread1.Join();
thread2.Join();
// 最終的なsharedVariableの値を表示(予測できない値になっている)
Console.WriteLine("Final shared variable value: " + sharedVariable);
// ウィンドウが閉じて値が見られなくなるのを防ぐため.文字入力まで待機
Console.ReadKey();
}
}
修正コードの結果
値が2000000となりました.これは100万回インクリメント×2スレッドの値であり,正しい結果が得られました.