멀티스레드를 활용할 경우가 많아지면서 자연스럽게 동기화 문제가 따라오게 되었습니다. 이 문제를 해결하기 위한 기본적인 방법은 중요한 지점(Critical Section)에 접근을 통제하는 것입니다. 이를 위한 방법론으로 lock, semaphore 등이 있는데 오늘은 Foundation에서 기본적으로 제공하는 NSLock과 관련 클래스들을 살펴보도록 하겠습니다.

  1. NSLocking
    모든 Lock 클래스가 채택하고 있는 프로토콜입니다. lock()과 unlock() 메소드를 필수로 구현하도록 요구합니다. 이 프로토콜을 채택한 Lock 객체들의 사용법은 거의 유사한데 다음과 같습니다.

     lock.lock() // lock을 시도하고, 성공할 때 까지 스레드가 block됩니다.
     // Critical Section
     lock.unlock() // lock을 해제하서, 다른 스레드가 들어갈 수 있도록 합니다.
    
  2. NSLock
    가장 기본적인 Lock 객체입니다. 위에서 제시한 lock(), unlock()을 제공함과 동시에 ‘lock을 얻을 때 까지 스레드를 block한다’는 점을 완화하기 위해 다음과 같은 메소드들을 제공합니다.

    1. lock(before:): 특정 시점까지 lock을 획득하도록 시도합니다. lock을 얻는 즉시 true를 반환하고, 특정 시점까지 lock을 획득하지 못한다면 false를 반환합니다. 이 메소드를 사용하면 특정 시점 이후에는 스레드가 block 되지 않음을 보장할 수 있습니다.

    2. try(): lock을 획득할 수 있는 지를 시도합니다. 성공하면 true, 실패하면 false를 반환합니다. 단발성으로 실행하기 때문에 스레드가 block되지 않습니다.

    그 외에도 Lock에 이름을 부여해 줄 수 있는 name 프로퍼티 등이 부가적으로 제공됩니다.

    NSLock은 lock을 거는 스레드와 unlock하는 스레드가 동일할 것을 요구합니다. 다른 스레드에서 unlock을 시도할 경우는 정의되지 않은 행동(=오류)이 발생합니다. 또한 같은 스레드에서 2번 이상 lock을 호출하면 스레드가 영원히 block됩니다.

  3. NSRecursiveLock
    NSLock과 비슷하나 동일 스레드에서 lock을 시도할 경우는 통과시키는 특성을 가지고 있습니다. 즉 동일 스레드에서는 여러번 lock을 걸 수 있고, 다른 스레드에서는 lock을 걸 수 없습니다. 내부적으로 lock을 건 횟수를 카운팅해서 카운트가 0이 되면 다른 스레드에서도 lock을 시도할 수 있습니다.

  4. NSConditionLock
    Lock을 획득하기 위한 조건을 설정할 수 있습니다. 이 조건은 단일 Int형의 값으로 나타나는데, 초깃값 혹은 unlock을 할 때 값을 바꿔줄 수 있습니다. 기존처럼 무조건 적으로 lock을 얻을 수도 있지만, 자신이 원하는 조건 값이 될 때에만 lock을 걸 수 있도록 제한을 할 수도 있습니다.

  5. NSCondition
    POSIX의 Condition Variable을 구현한 것입니다.참조 아래와 같은 과정을 통해 사용합니다.

    1. condition 변수에 lock() 메소드를 통해 lock을 겁니다.

    2. 조건이 될 때 까지 wait()을 호출하며 대기합니다.
      while (!(boolean_predicate)) {
           condition.wait()
       }
      
    3. 조건을 통과하게 되면 실제 작업을 수행해줍니다.

    4. 이후에 signal() 혹은 broadcast()를 호출하여 기다리고 있는 스레드들이 다시 조건을 체크할 수 있도록 합니다.

    5. condition의 lock을 해제합니다.