lock
ステートメントは、特定のオブジェクトの相互排他ロックを取得し、ステートメント ブロックを実行してから、ロックを解放します。 ロックが保持されている間、ロックを保持しているスレッドはロックを再度取得して解放できます。 他のスレッドはロックの取得をブロックされ、ロックが解放されるまで待機します。
lock
ステートメントを使用すると、任意の時点で最大 1 つのスレッドのみが本体を実行できます。
lock
ステートメントの形式は次のとおりです。
lock (x)
{
// Your code...
}
変数 x
は、 System.Threading.Lock 型または 参照型の式です。 コンパイル時に x
が System.Threading.Lock型であることがわかっている場合は、次とまったく同じです。
using (x.EnterScope())
{
// Your code...
}
Lock.EnterScope()によって返されるオブジェクトは、Dispose()
メソッドを含むref struct
です。 生成された using
ステートメントにより、 lock
ステートメントの本体で例外がスローされた場合でも、スコープが確実に解放されます。
それ以外の場合、 lock
ステートメントは次とまったく同じです。
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
コードでは try-finally
ステートメントを使用するため、 lock
ステートメントの本体内で例外がスローされた場合でもロックが解除されます。
lock
ステートメントの本文でawait
式を使用することはできません。
ガイドライン
.NET 9 および C# 13 以降では、最適なパフォーマンスを得るための System.Threading.Lock 型の専用オブジェクト インスタンスをロックします。 さらに、既知の Lock
オブジェクトが別の型にキャストされ、ロックされている場合、コンパイラは警告を発行します。 以前のバージョンの .NET と C# を使用している場合は、別の目的で使用されていない専用オブジェクト インスタンスをロックします。 デッドロックやロックの競合が発生する可能性があるため、異なる共有リソースに同じロック オブジェクト インスタンスを使用しないでください。 特に、ロック オブジェクトとして次のインスタンスを使用しないでください。
-
this
呼び出し元もthis
ロックする可能性があるためです。 - Typetypeof 演算子またはリフレクションによって取得される可能性があるため、インスタンス。
- 文字列リテラルを含む文字列インスタンスは、 インターンされる可能性があるためです。
ロックの競合を減らすために、できるだけ短い時間ロックを保持します。
例
次の例では、専用のbalanceLock
インスタンスをロックすることによって、プライベート balance
フィールドへのアクセスを同期するAccount
クラスを定義します。 ロックに同じインスタンスを使用すると、2 つの異なるスレッドがDebit
またはCredit
メソッドを同時に呼び出してbalance
フィールドを更新できなくなります。 このサンプルでは、C# 13 と新しい Lock
オブジェクトを使用します。 古いバージョンの C# または古い .NET ライブラリを使用している場合は、 object
のインスタンスをロックします。
using System;
using System.Threading.Tasks;
public class Account
{
// Use `object` in versions earlier than C# 13
private readonly System.Threading.Lock _balanceLock = new();
private decimal _balance;
public Account(decimal initialBalance) => _balance = initialBalance;
public decimal Debit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
}
decimal appliedAmount = 0;
lock (_balanceLock)
{
if (_balance >= amount)
{
_balance -= amount;
appliedAmount = amount;
}
}
return appliedAmount;
}
public void Credit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
}
lock (_balanceLock)
{
_balance += amount;
}
}
public decimal GetBalance()
{
lock (_balanceLock)
{
return _balance;
}
}
}
class AccountTest
{
static async Task Main()
{
var account = new Account(1000);
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => Update(account));
}
await Task.WhenAll(tasks);
Console.WriteLine($"Account's balance is {account.GetBalance()}");
// Output:
// Account's balance is 2000
}
static void Update(Account account)
{
decimal[] amounts = [0, 2, -3, 6, -2, -1, 8, -5, 11, -6];
foreach (var amount in amounts)
{
if (amount >= 0)
{
account.Credit(amount);
}
else
{
account.Debit(Math.Abs(amount));
}
}
}
}
C# 言語仕様
詳細については、C# 言語仕様の lock ステートメントのセクションを参照してください。
こちらも参照ください
.NET