NSLock vs NSRecursiveLock: 언제, 어떻게 사용하는 것이 최선인가?

작성일 :

NSLock vs NSRecursiveLock: 언제, 어떻게 사용하는 것이 최선인가?

동시성 프로그래밍에서 가장 큰 과제 중 하나는 데이터 경합을 방지하는 것입니다. 여러 스레드가 동일한 자원에 동시에 접근하려고 할 때, 데이터가 손상되거나 예기치 않은 동작이 발생할 수 있습니다. 이를 방지하기 위해 락(lock)을 사용하여 자원의 접근을 제어합니다. Swift에서는 대표적으로 NSLockNSRecursiveLock을 사용할 수 있습니다. 이번 글에서는 이 두 가지 락의 차이점과 각각을 언제 어떻게 사용하는 것이 최선인지 알아보겠습니다.

NSLock

NSLock은 가장 기본적인 형태의 락으로, 단일 스레드가 자원에 접근하는 동안 다른 스레드의 접근을 차단합니다. 다음은 NSLock의 주요 특징입니다:

  • 간편성: NSLock은 상대적으로 사용하기 간단합니다. lock(), unlock() 메소드를 통해 자원을 잠그고 풀 수 있습니다.
  • 고성능: 상당히 가벼운 락으로, 성능이 중요한 경우에 유리합니다.
  • 비재귀적: 동일 스레드 내에서 중복 잠금을 허용하지 않습니다. 동일 스레드가 이미 잠긴 락을 다시 잠그려고 하면 데드락(교착 상태)이 발생합니다.

사용 예시

swift
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock()
    sharedResource += 1
    lock.unlock()
}

DispatchQueue.concurrentPerform(iterations: 10) { _ in
    modifyResource()
}

print(sharedResource)

위 예시에서 여러 스레드가 동시에 modifyResource() 함수를 호출할 수 있지만, NSLock을 사용하여 sharedResource에 접근하는 것을 직렬화(순서대로 처리)할 수 있습니다.

NSRecursiveLock

NSRecursiveLockNSLock과 매우 유사하지만, 같은 스레드에서 중복 잠금을 허용한다는 중요한 차이점이 있습니다. 이는 특정 상황에서 매우 유용할 수 있습니다.

주요 특징

  • 재귀적 잠금 허용: 동일 스레드가 동일한 락을 여러 번 잠글 수 있으며, 잠금 횟수만큼 풀어야 다른 스레드가 접근할 수 있습니다.
  • 복잡한 알고리즘: 재귀적 알고리즘이나 재귀적 함수 호출에 적합합니다.
  • 오버헤드: 일반적으로 NSLock보다 약간 더 많은 오버헤드가 있지만, 특정 상황에서 이를 통해 코드의 간결함과 안전성을 확보할 수 있습니다.

사용 예시

swift
import Foundation

let recursiveLock = NSRecursiveLock()
var recursiveSharedResource = 0

func recursiveFunction(count: Int) {
    recursiveLock.lock()
    if count > 0 {
        recursiveSharedResource += 1
        recursiveFunction(count: count - 1) // 재귀 호출
    }
    recursiveLock.unlock()
}

recursiveFunction(count: 10)
print(recursiveSharedResource)

위 예시에서는 recursiveFunction이 재귀적으로 호출될 때마다 동일한 락을 여러 번 잠그지만, NSRecursiveLock 덕분에 문제가 발생하지 않습니다. 마지막에 모든 락이 풀리면 다른 스레드가 recursiveSharedResource에 접근할 수 있습니다.

언제, 어떻게 사용할 것인가?

이제 NSLockNSRecursiveLock의 차이점과 사용 예시를 살펴봤습니다. 그렇다면 언제 각각을 사용하는 것이 최선일까요?

NSLock 사용 시기

NSLock을 사용하는 것이 더 적합한 경우는 다음과 같습니다:

  • 성능이 중요한 경우: NSLock은 락의 오버헤드가 적으므로, 성능이 중요한 상황에서 유리합니다.
  • 비재귀적 코드: 동일 스레드에서 중복 잠금이 발생하지 않는 경우에 적합합니다.

NSRecursiveLock 사용 시기

NSRecursiveLock을 사용하는 것이 더 적합한 경우는 다음과 같습니다:

  • 재귀적 알고리즘: 재귀적 함수나 알고리즘을 구현할 때 유리합니다.
  • 복잡한 코드: 동일 스레드에서 중복 잠금이 발생할 수 있는 복잡한 시나리오에서 적합합니다.

결론

Swift에서 동시성 문제를 해결하는 대표적인 방법인 NSLockNSRecursiveLock에 대해 살펴봤습니다. 각각의 특성과 사용 사례를 이해함으로써, 상황에 맞는 최적의 락을 선택하여 안정적이고 효율적인 코드를 작성할 수 있습니다. NSLock은 비재귀적 상황에서 고성능을 제공하며, NSRecursiveLock은 재귀적 알고리즘에서 코드의 간결함과 안정성을 제공합니다. 각자 상황에 맞는 락을 잘 선택하고 활용하는 것이 중요합니다.