다음을 통해 공유


lock 문 - 공유 리소스에 대한 단독 액세스 보장

이 문은 lock 지정된 개체에 대한 상호 배제 잠금을 획득하고 문 블록을 실행한 다음 잠금을 해제합니다. 잠금이 유지되는 동안 잠금을 보유하는 스레드는 잠금을 다시 획득하고 해제할 수 있습니다. 다른 스레드는 잠금을 획득하지 못하도록 차단되고 잠금이 해제될 때까지 기다립니다. 이 문은 lock 한 번에 최대 하나의 스레드만 본문을 실행하도록 합니다.

이 문은 lock 다음과 같은 형식을 사용합니다.

lock (x)
{
    // Your code...
}

변수 x 는 형식의 System.Threading.Lock 식이거나 참조 형식입니다. 컴파일 타임에 형식System.Threading.Lock으로 알려진 경우 x 다음과 정확히 동일합니다.

using (x.EnterScope())
{
    // Your code...
}

반환되는 Lock.EnterScope() 개체는 메서드를 ref struct 포함하는 개체입니다 Dispose() . 생성된 using 문은 문 본문 lock 에 예외가 throw되더라도 범위가 해제되도록 합니다.

그렇지 않으면 문 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 내에서 예외가 throw되더라도 잠금이 해제됩니다.

문 본문 lock 에는 await 식을 사용할 수 없습니다.

지침

.NET 9 및 C# 13부터 최상의 성능을 위해 형식의 전용 개체 인스턴스를 System.Threading.Lock 잠급 수 있습니다. 또한 알려진 Lock 개체가 다른 형식으로 캐스팅되고 잠겨 있으면 컴파일러에서 경고를 발생합니다. 이전 버전의 .NET 및 C#을 사용하는 경우 다른 용도로 사용되지 않는 전용 개체 인스턴스를 잠급니다. 교착 상태 또는 잠금 경합이 발생할 수 있으므로 다른 공유 리소스에 동일한 잠금 개체 인스턴스를 사용하지 마세요. 특히 다음 인스턴스를 잠금 개체로 사용하지 않도록 합니다.

  • this- 호출자가 잠글 this수도 있으므로 .
  • Type typeof 연산자 또는 리플렉션에서 가져올 수 있는 인스턴스입니다.
  • 문자열 리터럴을 포함한 문자열 인스턴스는 인턴될 수 있습니다.

잠금 경합을 줄이기 위해 가능한 한 짧은 시간 동안 잠금을 유지합니다.

예시

다음 예제에서는 전용 balanceLock 인스턴스를 잠그고 프라이빗 balance 필드에 대한 액세스를 동기화하는 클래스를 정의 Account 합니다. 잠금에 동일한 인스턴스를 사용하면 두 스레드가 동시에 또는 Credit 메서드를 balance 호출 Debit 하여 필드를 업데이트할 수 없습니다. 샘플에서는 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 문 섹션을 참조하세요.

참고하십시오