NSLock vs NSRecursiveLock: 언제, 어떻게 사용하는 것이 최선인가?
NSLock vs NSRecursiveLock: 언제, 어떻게 사용하는 것이 최선인가?
동시성 프로그래밍에서 가장 큰 과제 중 하나는 데이터 경합을 방지하는 것입니다. 여러 스레드가 동일한 자원에 동시에 접근하려고 할 때, 데이터가 손상되거나 예기치 않은 동작이 발생할 수 있습니다. 이를 방지하기 위해 락(lock)을 사용하여 자원의 접근을 제어합니다. Swift에서는 대표적으로 NSLock
과 NSRecursiveLock
을 사용할 수 있습니다. 이번 글에서는 이 두 가지 락의 차이점과 각각을 언제 어떻게 사용하는 것이 최선인지 알아보겠습니다.
NSLock
NSLock
은 가장 기본적인 형태의 락으로, 단일 스레드가 자원에 접근하는 동안 다른 스레드의 접근을 차단합니다. 다음은 NSLock
의 주요 특징입니다:
- 간편성:
NSLock
은 상대적으로 사용하기 간단합니다.lock()
,unlock()
메소드를 통해 자원을 잠그고 풀 수 있습니다. - 고성능: 상당히 가벼운 락으로, 성능이 중요한 경우에 유리합니다.
- 비재귀적: 동일 스레드 내에서 중복 잠금을 허용하지 않습니다. 동일 스레드가 이미 잠긴 락을 다시 잠그려고 하면 데드락(교착 상태)이 발생합니다.
사용 예시
swiftimport 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
NSRecursiveLock
은 NSLock
과 매우 유사하지만, 같은 스레드에서 중복 잠금을 허용한다는 중요한 차이점이 있습니다. 이는 특정 상황에서 매우 유용할 수 있습니다.
주요 특징
- 재귀적 잠금 허용: 동일 스레드가 동일한 락을 여러 번 잠글 수 있으며, 잠금 횟수만큼 풀어야 다른 스레드가 접근할 수 있습니다.
- 복잡한 알고리즘: 재귀적 알고리즘이나 재귀적 함수 호출에 적합합니다.
- 오버헤드: 일반적으로
NSLock
보다 약간 더 많은 오버헤드가 있지만, 특정 상황에서 이를 통해 코드의 간결함과 안전성을 확보할 수 있습니다.
사용 예시
swiftimport 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
에 접근할 수 있습니다.
언제, 어떻게 사용할 것인가?
이제 NSLock
과 NSRecursiveLock
의 차이점과 사용 예시를 살펴봤습니다. 그렇다면 언제 각각을 사용하는 것이 최선일까요?
NSLock
사용 시기
NSLock
을 사용하는 것이 더 적합한 경우는 다음과 같습니다:
- 성능이 중요한 경우:
NSLock
은 락의 오버헤드가 적으므로, 성능이 중요한 상황에서 유리합니다. - 비재귀적 코드: 동일 스레드에서 중복 잠금이 발생하지 않는 경우에 적합합니다.
NSRecursiveLock
사용 시기
NSRecursiveLock
을 사용하는 것이 더 적합한 경우는 다음과 같습니다:
- 재귀적 알고리즘: 재귀적 함수나 알고리즘을 구현할 때 유리합니다.
- 복잡한 코드: 동일 스레드에서 중복 잠금이 발생할 수 있는 복잡한 시나리오에서 적합합니다.
결론
Swift에서 동시성 문제를 해결하는 대표적인 방법인 NSLock
과 NSRecursiveLock
에 대해 살펴봤습니다. 각각의 특성과 사용 사례를 이해함으로써, 상황에 맞는 최적의 락을 선택하여 안정적이고 효율적인 코드를 작성할 수 있습니다. NSLock
은 비재귀적 상황에서 고성능을 제공하며, NSRecursiveLock
은 재귀적 알고리즘에서 코드의 간결함과 안정성을 제공합니다. 각자 상황에 맞는 락을 잘 선택하고 활용하는 것이 중요합니다.