iOS 교착상태 해결

치킨치·2024년 5월 7일
0

기술을 구현하기 위해 공유자원를 사용하게 되는데, 이 부분에서 교착상태(Deadlock)가 발생하지 않도록 커널과 하드웨어에서 문제가 없도록 보장해줘야 한다.
참고로 이 문제를 해결하기 위해 busy waiting, sleeping, sleep and wake up 기술을 이용한 프로그램 변수를 사용하거나 실제 파일을 만들어 구현하기도 한다고 한다
NSLock, DispatchSemaphore, DispatchGroup, Actor 등은 위의 이슈를 해결하기 위해 Apple이 구현해놓은 인터페이스라고 할 수 있다.
이 글에서는 컴퓨터공학에서 이야기하는 단어들을 먼저 설명하고, iOS에서 어떻게 이를 사용하고 있는지를 이어서 설명하겠다.

멀티스레드 프로그래밍

뮤텍스(Mutex)

Mutex라는 이름은 Mutual Exclusion 즉 상호배제라는 단어에서 왔다.
상호배제는 뮤텍스만이 가진 기능이 아니므로 개념을 혼동하면 안될 것이다.
(작성중)

세마포어(Semaphore)

리소스가 여러개일 때
이진 세마포어와 카운팅 세마포어
이진 세마포어를 사용하는 lock
(작성중)

스핀락(Spinlock)

스레드가 락을 얻을 때까지 무한 루프를 돌며 확인하는 동기화 매커니즘이다.
경합상황이 매우 짧을 때 유리할 때, 즉 OS의 context switching 비용이 더 발생한다고 판단할 때 사용한다.
busy wating이므로 스핀락 획득까지 cpu 리소스 낭비를 하고, lock 획득이 빠르기 때문에 다른 쓰레드의 자원획득을 방해하는 기아상태를 야기 할 수 있다.

Apple System Framework

과거 Apple에선 POSIX 표준함수를 사용하여 쓰레드를 직접 다루었지만 새로운 함수를 구현 및 대체하여 기기 최적화를 달성을 꾀했다. 기존에도 NSLock, OSSpinLock 처럼 래핑해

NSLock

POSIX 스레드 라이브러리를 사용해 뮤텍스를 구현한다.
pthread_mutex_trylock 또는 pthread_mutex_lock로 뮤텍스를 잠그고, pthread_mutex_unlock로 잠금을 해제하고 있다.

iOS10 이후에 os_unfair_lock_lock, os_unfair_lock_unlock 처럼 Apple 기기에 최적화된 새로운 락 인터페이스가 발표되었지만 NSLock에 적용되지 않은 것으로 보인다. (OSSpinLock을 대체하기 위해서라고 밝히고 있다)
예상컨데 NSLock처럼 NS prefix를 사용하는 인터페이스들은 레거시로 간주해 업데이트를 멈추고, GCD/Foundation/UIKit/CoreData 등의 구현을 새로운 인터페이스로 대체한 것으로 보인다.

Grand Central Dispatch

일명 GCD.
일반적으로 최대공약수(Greatest Common Divisor)를 의미하지만, Apple System에선 멀티코어 프로세서에 코드를 동시에 실행시키게 해주는 프레임워크를 지칭한다.
앞으로 WWDC에서 GCD란 단어가 나온다면 '멀티스레드 프로그래밍관련 인터페이스가 업데이트 되었구나' 정도로 생각하면 될 것이다.
NextStep(이하 NS) 탈피이후 GCD에서 DispatchSemaphore, DispatchQueues, DispatchQueues 처럼 prefix를 사용해 GCD 시스템의 일원인 것을 쉽게 알 수 있다.
이런 사실들로 NSLock은 GCD이 개발되기 전에 있었던 독립적인 인터페이스라는 걸 이름으로도 유추 할 수 있을 것이다.

DispatchSemaphore

(https://developer.apple.com/documentation/dispatch/dispatchsemaphore)
이름에서 쉽게 알아볼 수 있듯 Semaphore를 기반으로 구현되어 있다.
(작성중)

DispatchGroup

(작성중)

DispatchQueues

(작성중)

Swift Concurrency

WWDC2021에서 소개된 새로운 동시성 프로그래밍 인터페이스다.
새로운 인터페이스가 추가된 이유가 된 GCD의 문제점을 이야기해보자:
1. 콜백합수가 중첩되는 콜백지옥에 빠질 수 있다.
클로져가 있다는 건 데이터캡쳐 문제의 가능성이 생기므로 메모리누수를 신경써야 한다.
이 문제는 async/await라는 새로운 문법을 추가해서 해결 할 수 있었다고 한다.

  1. thread swithing overhead
    task는 await 실행으로 시스템에게 일시중단(suspend)를 요청해 스레드의 제어권을 놓아주는데, await가 끝나고 제어권이 다시 넘어올 때 task가 실행되는 스레드가 이전과 동일하지 않을 수 있다.

SwiftUI의 심플한 네이밍 기조를 따르기 위해 Actor, Sendable, Task 등의 새로운 개념이 나오면서 이 법칙을 깨트린 것으로 보인다.

(작성중)

actor Type


struct, class와 같은 object 타입 레벨로 데이터 구조를 정의한다.
actor 타입은 명시적 잠금 없이 공유 리소스를 처리하는 데 이상적이다.
(작성중)

@{XXX}Actor

'@'심볼은 이용한 컴파일러 지시문 중 하나인 property wrapper를 의미한다.
키워드의 스펠링는은 같지만 위에서 설명한 type과 별개라는 것을 명심해야 한다.

actor Sample {
    let id = UUID().uuidString
    @MainActor var myProperty: String // 해당 property 는 main thread 에서만 접근 가능
    
    init(_ myProperty: String) {
        self.myProperty = myProperty
    }
    
    // 해당 메서드는 main thread 에서만 호출 가능
    @MainActor func changeMyProperty(to newValue: String) {
        self.myProperty = newValue
    }
    
    func changePropertyToName() {
        Task { @MainActor in  // block 안에 들어있는 코드도 main actor 에서 실행
            myProperty = "naljin"
        }
    }
}
profile
풀스텍이었던 iOS개발자

0개의 댓글