Share via


lock 문 - 공유 리소스에 대한 단독 액세스 확인

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

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 문의 본문에 예외가 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 클래스를 정의합니다. 잠금을 위해 동일한 인스턴스를 사용하면 두 개의 다른 스레드가 동시에 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 문 섹션을 참조하세요.

참고 항목