lock ステートメント - 共有リソースへの排他的なアクセスを保証します。
lock
ステートメントは、指定のオブジェクトに対する相互排他ロックを取得し、ステートメント ブロックを実行してからロックを解放します。 ロックが保持されている間、ロックを保持するスレッドではロックを再度取得し、解放することができます。 他のスレッドはブロックされてロックを取得できず、ロックが解放されるまで待機します。 lock
ステートメントにより常に、最大で 1 つだけのスレッドでその本文が実行されます。
lock
ステートメントの形式は次のようになります。
lock (x)
{
// Your code...
}
x
は参照型の式です。 これは次にまったく等しくなります。
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
式を使用することはできません。
ガイドライン
共有リソースへのスレッド アクセスを同期する場合、専用オブジェクト インスタンス (private readonly object balanceLock = new object();
など) またはコードの関連のない部分によってロック オブジェクトとして使用される可能性がない別のインスタンスをロックします。 異なる共有リソースに対して同じロック オブジェクト インスタンスを使用することは避けてください。デッドロックやロックの競合が発生する可能性があります。 特に、次のインスタンスをロック オブジェクトとして使用しないでください。
this
。ロックとして呼び出し元に使用される可能性があります。- Type インスタンス。typeof 演算子またはリフレクションによって取得される可能性があります。
- 文字列リテラルを含む文字列インスタンス。インターン処理される可能性があります。
ロックの競合を減らすために、できるだけ短い時間ロックを保持します。
例
次の例では、専用 balanceLock
インスタンスをロックすることでそのプライベート balance
フィールドへのアクセスを同期する Account
クラスが定義されます。 ロッキングに同じインスタンスを使用すると、2 つのスレッドが Debit
または Credit
メソッドを同時に呼び出すことによって balance
フィールドを同時に更新することができなくなります。
using System;
using System.Threading.Tasks;
public class Account
{
private readonly object balanceLock = new object();
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 ステートメントに関するセクションを参照してください。