[Swift] Singleton Multithread Strategy / 싱글톤 멀티스레드 전략
2024. 12. 27. 22:52ㆍ🍏/DesignPattern
안전한 Singleton Pattern을 위환 Thread-Safe 관리
우리는 앱에서 전역적인 리소스 공유, 앱 상태 관리, 네트워크 요청, 로깅 및 분석 등과 같은 곳에서 싱글톤 패턴을 활용합니다.
이때 어디서든 동일한 인스턴스에 접근하게 되고, 앱이 복잡해질수록 데이터 레이스가 발생할 가능성이 높아질 수 있습니다. 이때 사용할 수 있는 방안에 대해서 몇 가지 예시를 제시합니다.
1. DispatchQueue를 활용한 데이터 동기화
final class Singleton {
static let shared = Singleton()
private let queue = DispatchQueue(label: "com.singleton.threadsafe", attributes: .concurrent)
private var internalData: [String] = [] // 내부 데이터
private init() {}
// 데이터 읽기: 동시적 읽기 허용
func getData() -> [String] {
return queue.sync {
internalData
}
}
// 데이터 쓰기: 동기화하여 단일 스레드만 접근
func addData(_ newData: String) {
queue.async(flags: .barrier) { // Barrier로 쓰기 작업 보호
self.internalData.append(newData)
}
}
}
- 읽기 작업 (sync):
- 데이터를 읽을 때는 sync를 사용하여 Thread-Safe하게 읽음.
- 동시 읽기(Concurrent Read)는 안전하므로 추가 동기화 없이 처리.
- 쓰기 작업 (async + barrier):
- barrier 플래그를 사용하여 쓰기 작업이 실행되는 동안 다른 읽기/쓰기 작업을 차단.
2. NSLock을 사용한 데이터 동기화
final class Singleton {
static let shared = Singleton()
private let lock = NSLock()
private var internalData: [String] = [] // 내부 데이터
private init() {}
// 데이터 읽기
func getData() -> [String] {
lock.lock()
defer { lock.unlock() }
return internalData
}
// 데이터 쓰기
func addData(_ newData: String) {
lock.lock()
defer { lock.unlock() }
internalData.append(newData)
}
}
- lock.lock():
- 특정 스레드가 데이터에 접근하기 전에 Lock을 걸어 다른 스레드의 접근을 차단.
- lock.unlock():
- 작업이 끝난 후 Lock을 해제하여 다른 스레드가 접근 가능하도록 함.
3. OperationQueue를 사용한 데이터 동기화
final class Singleton {
static let shared = Singleton()
private let operationQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1 // 직렬 실행
return queue
}()
private var internalData: [String] = [] // 내부 데이터
private init() {}
// 데이터 읽기
func getData(completion: @escaping ([String]) -> Void) {
operationQueue.addOperation {
completion(self.internalData)
}
}
// 데이터 쓰기
func addData(_ newData: String) {
operationQueue.addOperation {
self.internalData.append(newData)
}
}
}
- 작업 큐 기반:
- OperationQueue는 작업을 순서대로 처리하여 Thread Safety를 보장.
- 비동기 작업 처리:
- 쓰기 작업이나 대량의 데이터를 처리할 때 유용.
3. Thread-Safe한 싱글톤 데이터 접근 방식 비교
방법 | 장점 | 단점 |
DispatchQueue | - 간단하고 효율적. - Barrier로 쓰기 보호. |
- Custom Lock보다 추상적. |
NSLock | - Lock 메커니즘으로 직접 제어 가능. | - 코드가 길어질 수 있음. - 성능 저하 가능. |
OperationQueue | - 비동기 작업 처리. - 작업 관리 용이. |
- 작업 큐 관리가 필요. - 간단한 작업엔 과도. |
4. 각 방식의 동작 원리
- DispatchQueue
- barrier 작업은 이전에 큐에 추가된 모든 작업이 완료된 후 실행되며, barrier 작업이 완료될 때까지 이후의 작업이 실행되지 않습니다.
- NSLock
- Mutex(Mutual Exclusion, 상호 배제) 락의 구현체
- Deadlock을 조심해야함
- 병렬 작업 성능 저하
- 락 경쟁 비용 - OS 커널 레벨에서 동작하는 락 메커니즘이므로 컨텍스트 스위칭이 오래 걸릴 수 있다.
- OperationQueue
- OperationQueue와 BlockOperation을 사용하여 비동기 작업 간 Thread Safety를 관리
그 외 Lock 성능 비교
동기화 메커니즘 | 컨텍스트 스위칭 | Thread-Safe | 성능 | 특징 |
NSLock | 있음 | 보장 | 느림 (커널 오버헤드) | 간단한 락, 중간 수준의 성능 |
DispatchQueue | 없음 | 보장 | 빠름 | 읽기-쓰기 동시성 관리에 유리 |
NSRecursiveLock | 있음 | 보장 | 느림 (커널 오버헤드) | 재귀 락 지원 |
os_unfair_lock | 없음 | 보장 | 매우 빠름 | 경량화된 저수준 락, Spin Lock 기반 |
'🍏 > DesignPattern' 카테고리의 다른 글
[iOS] Delegate Pattern / 델리게이트(위임) 패턴 (0) | 2024.12.30 |
---|---|
[CS] MVC design pattern / MVC 디자인 패턴 (0) | 2022.10.21 |