수색…


통사론

  • 잠금 (obj) {}

비고

lock 문을 사용하면 코드 블록 내의 코드에 대한 서로 다른 스레드의 액세스를 제어 할 수 있습니다. 경쟁 조건을 방지하기 위해 일반적으로 사용됩니다 (예 : 컬렉션에서 항목을 읽고 제거하는 다중 스레드). 잠금은 다른 스레드가 코드 블록을 종료 할 때까지 대기하도록 스레드를 강제하므로 다른 동기화 방법으로 해결할 수있는 지연이 발생할 수 있습니다.

MSDN

lock 키워드는 주어진 객체에 대한 상호 배제 잠금을 얻고, 명령문을 실행 한 다음 잠금을 해제하여 명령문 블록을 중요한 섹션으로 표시합니다.

lock 키워드는 한 스레드가 다른 스레드가 critical 섹션에있는 동안 코드의 중요한 섹션에 들어가지 않도록합니다. 다른 스레드가 잠긴 코드를 입력하려고하면 객체가 해제 될 때까지 블록을 기다립니다.

모범 사례는 잠글 사설 개체를 정의하거나 모든 인스턴스에 공통된 데이터를 보호하기 위해 개인 정적 개체 변수를 정의하는 것입니다.


C # 5.0 이상에서 lock 문은 다음과 같습니다.

bool lockTaken = false;
try 
{
    System.Threading.Monitor.Enter(refObject, ref lockTaken);
    // code 
}
finally 
{
    if (lockTaken)
        System.Threading.Monitor.Exit(refObject);
}

C # 4.0 이하의 경우 lock 문은 다음과 같습니다.

System.Threading.Monitor.Enter(refObject);
try 
{
    // code
}
finally 
{
     System.Threading.Monitor.Exit(refObject);
}

간단한 사용법

lock 일반적인 사용법은 중요한 부분입니다.

다음 예제에서 ReserveRoom 은 다른 스레드에서 호출되어야합니다. lock 동기화가 경쟁 조건을 방지하는 가장 간단한 방법입니다. 메소드 본문은 둘 이상의 스레드가 동시에 실행할 수 없도록하는 lock 으로 둘러싸여 있습니다.

public class Hotel
{
    private readonly object _roomLock = new object();

    public void ReserveRoom(int roomNumber)
    {
        // lock keyword ensures that only one thread executes critical section at once
        // in this case, reserves a hotel room of given number
        // preventing double bookings
        lock (_roomLock)
        {
            // reserve room logic goes here
        }
    }
}

쓰레드가 도달하면 lock 다른 스레드가 그 안에서 실행되는 동안 -ed 블록을, 이전 블록을 종료하기 위해 다른 대기합니다.

모범 사례는 잠글 사설 개체를 정의하거나 모든 인스턴스에 공통된 데이터를 보호하기 위해 개인 정적 개체 변수를 정의하는 것입니다.

lock 문에 예외 발생

다음 코드는 잠금을 해제합니다. 문제 없습니다. 숨겨진 진술은 try finally 작동합니다.

lock(locker)
{
    throw new Exception();
}

더 많은 내용은 C # 5.0 사양 에서 확인할 수 있습니다.

양식에 대한 lock

lock (x) ...

여기서 x참조 유형 의 표현식과 정확하게 동일합니다.

bool __lockWasTaken = false;
try {
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally {
    if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}

x 는 한 번만 계산됩니다.

잠금 문에서 반환

다음 코드는 잠금을 해제합니다.

lock(locker)
{
    return 5;
}

자세한 설명은 이 SO 답변 을 권장합니다.

잠금을 위해 Object의 인스턴스 사용하기

C #의 inbuilt lock 문을 사용하면 일부 유형의 인스턴스가 필요하지만 상태는 중요하지 않습니다. object 의 인스턴스는 다음과 같이 완벽합니다.

public class ThreadSafe {
  private static readonly object locker = new object();


  public void SomeThreadSafeMethod() {
    lock (locker) {
      // Only one thread can be here at a time.
    }
  }
}

NB . 인스턴스 Type (위의 코드에서이 사용되지 않아야 typeof(ThreadSafe) 의 인스턴스 때문에) Type 응용 프로그램 도메인간에 공유되고, 따라서 로크의 범위는 예상대로 말아야 코드를 포함 할 수있다 (예. 만약 ThreadSafe 에로드 같은 프로세스에있는 두 개의 AppDomain과 Type 인스턴스를 잠그면 상호 잠금됩니다.

반 패턴 및 잡았다

스택 할당 / 로컬 변수에 대한 잠금

lock 을 사용하는 동안의 오류 중 하나는 함수에서 로컬 객체를 라커로 사용하는 것입니다. 이러한 로컬 객체 인스턴스는 함수 호출마다 다를 것이므로 lock 은 예상대로 수행되지 않습니다.

List<string> stringList = new List<string>();

public void AddToListNotThreadSafe(string something)
{
    // DO NOT do this, as each call to this method 
    // will lock on a different instance of an Object.
    // This provides no thread safety, it only degrades performance.
    var localLock = new Object();
    lock(localLock)
    {
        stringList.Add(something);
    }
}

// Define object that can be used for thread safety in the AddToList method
readonly object classLock = new object();

public void AddToList(List<string> stringList, string something)
{
    // USE THE classLock instance field to achieve a 
    // thread-safe lock before adding to stringList
    lock(classLock)
    {
        stringList.Add(something);
    }
}

잠금은 동기화 객체 자체에 대한 액세스를 제한한다고 가정합니다.

한 스레드가 lock(obj) 을 호출하고 다른 스레드가 obj.ToString() 호출 obj.ToString() 두 번째 스레드가 차단되지 않습니다.

object obj = new Object();
 
public void SomeMethod()
{
     lock(obj)
    {
       //do dangerous stuff 
    }
 }

 //Meanwhile on other tread 
 public void SomeOtherMethod()
 {
   var objInString = obj.ToString(); //this does not block
 }

잠글 때를 알기 위해 서브 클래스 기대

때로는 기본 클래스는 하위 클래스가 특정 보호 된 필드에 액세스 할 때 잠금을 사용하도록 설계되었습니다.

public abstract class Base
{
    protected readonly object padlock;
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public abstract void Do();
}

public class Derived1 : Base
{
    public override void Do()
    {
        lock (this.padlock)
        {
            this.list.Add("Derived1");
        }
    }
}

public class Derived2 : Base
{
    public override void Do()
    {
        this.list.Add("Derived2"); // OOPS! I forgot to lock!
    }
}

템플릿 메서드 를 사용하여 잠금캡슐화하는 것이 훨씬 안전합니다.

public abstract class Base
{
    private readonly object padlock; // This is now private
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public void Do()
    {
        lock (this.padlock) {
            this.DoInternal();
        }
    }

    protected abstract void DoInternal();
}

public class Derived1 : Base
{
    protected override void DoInternal()
    {
        this.list.Add("Derived1"); // Yay! No need to lock
    }
}

박스형 ValueType 변수에 대한 잠금이 동기화되지 않습니다.

다음 예제에서는 모니터 자원이 잠길 것으로 예상하여 함수에 대한 object 인수로 제공 될 때 개인 변수가 암시 적으로 박스 처리됩니다. boxed 인스턴스는 함수가 호출 될 때마다 다른 힙 개체에 해당하도록 IncInSync 함수를 호출하기 직전에 발생합니다.

public int Count { get; private set; }

private readonly int counterLock = 1;

public void Inc()
{
    IncInSync(counterLock);
}

private void IncInSync(object monitorResource)
{
    lock (monitorResource)
    {
        Count++;
    }
}

권투는 Inc 함수에서 발생합니다.

BulemicCounter.Inc:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+BulemicCounter.counterLock
IL_0008:  box         System.Int32**
IL_000D:  call        UserQuery+BulemicCounter.IncInSync
IL_0012:  nop         
IL_0013:  ret         

Boxed ValueType을 모니터 잠금에 전혀 사용할 수 없다는 의미는 아닙니다.

private readonly object counterLock = 1;

이제 boxing은 생성자에서 발생하는데 잠금에 문제가 없습니다.

IL_0001:  ldc.i4.1    
IL_0002:  box         System.Int32
IL_0007:  stfld       UserQuery+BulemicCounter.counterLock

안전한 대안이있을 때 불필요하게 잠금 사용

매우 일반적인 패턴은 스레드 안전 클래스에서 개인 List 또는 Dictionary 을 사용하고 액세스 할 때마다 잠그는 것입니다.

public class Cache
{
    private readonly object padlock;
    private readonly Dictionary<string, object> values;

    public WordStats()
    {
        this.padlock = new object();
        this.values = new Dictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        lock (this.padlock)
        {
            this.values.Add(key, value);
        }
    }

    /* rest of class omitted */
}

values 사전에 액세스하는 여러 메소드가있는 경우 코드가 매우 길어질 수 있으며 더 중요한 것은 모든 시간을 잠그면 그 의도가 흐려집니다. 잠금은 잊기가 매우 쉽고 적절한 잠금이 없으면 버그를 찾기가 어려울 수 있습니다.

ConcurrentDictionary 를 사용하면 완전히 잠그지 않게 할 수 있습니다.

public class Cache
{
    private readonly ConcurrentDictionary<string, object> values;

    public WordStats()
    {
        this.values = new ConcurrentDictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        this.values.Add(key, value);
    }

    /* rest of class omitted */
}

동시 콜렉션을 사용하면 모든 것이 잠금없는 기술 을 어느 정도 사용 하기 때문에 성능이 향상됩니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow