A colossal Dreamer: GR鐵塔-天生我材

Multi Thread 에서 접근하는 안전한 Property 설계 GCD (DispatchQueue) 본문

Development/아이폰

Multi Thread 에서 접근하는 안전한 Property 설계 GCD (DispatchQueue)

江多林 2019. 10. 23. 15:06
    private let accessQueue = DispatchQueue(label: "SynchronizedAccess",
                                            attributes: .concurrent)

    private var _someValue: [Int]
    var someValue: [Int] {
        get {
            var someValue: [Int] = []
            accessQueue.sync {
            	someValue = _someValue
            }
            return someValue
        }
        set {
            accessQueue.async(flags: .barrier) {
                self.willChangeValue(for: \.someValue)
                self._someValue = newValue
                self.didChangeValue(for: \.someValue)
            }
        }
    }        	

코드 설명,

accessQueue 생성할 때 .concurrent 를 명시했다.

    Serial Queue를 사용할 경우에 요청한 순서대로 실행되므로,

    서로 다른 쓰레드에서 동시에 접근한 경우 뒤에 읽기 동작이 앞의 읽기 동작이 끝날 때까지 대기하게 되어

    UX가 멈칫하는 등의 성능저하가 발생할 수 있다.

 

getter 읽기 동작시에는 sync 로 값을 읽고 리턴한다.

    이 때 sync 로 호출했지만, .concurrent 속성으로 인해서 서로 다른 쓰레드의 동시 접근도 동시에 실행가능하다.

setter 쓰기 동작시에는 async 를 사용했지만, .barrier 플래그를 활용해

    이전의 모든 sync 읽기 동작이 끝난 후에 배타적으로(이후에 요청된 다른 동작을 대기시키고,) 값을 설정하게 된다.

 

위 기법은 멀티프로세서에서 동시에 서로 다른 CPU 에서 읽기와 쓰기가 발생하면서,

성능이 저하되거나 잘못된 값을 읽는 문제도 해결하고 있다.

변경하는 동안에는 하나의 쓰레드에서만 저장소에 접근하므로,

동시에 서로 다른 CPU 에서 쓰기+쓰기, 쓰기+읽기 모두 발생할 수 없다.

이것은 쓰기 동작이 끝난 후에 CPU.cache 가 갱신되므로,

이후에는 서로 다른 CPU 에서 읽기를 동시에 처리하더라도 잘못된 값을 읽지 않는다.

 

주의

accessQueue 가 외부에 노출되어서는 곤란하다.

someValue getter 에서 sync 로 요청되므로, 외부에서 accessQueue.sync 블록 내부에서

someValue getter 를 호출할 경우에 크래시가 발생할 수 있고,

someValue setter 에서 .barrier 를 사용하므로, 외부에서 accessQueue sync/async 블록 내부에서

someValue setter 를 호출할 경우 교착상태에 빠져 현재 처리 Thread 와 DispatchQueue 모두 멈춘다.

 

참고

* accessQueue.async(flags: .barrier) {} 에 대해서.

동시에 요청된 동작들이 끝날 때까지 기다려서 .barrier 속성을 가진 DispatchWorkItem 이 실행된다.

또한 .barrier 속성의 DispatchWorkItem 요청 이후의 처리는 .barrier 속성의 DispatchWorkItem 이 끝날 때 까지 대기 한 후에 실행된다.

 

* DispatchQueue 와 Thread 의 관계

DispatchQueue 와 Thread 를 동일시 해서는 곤란하다.

DispatchQueue 가 매번 동일한 Thread 에서 실행되지 않고, 종종 Thread 가 변경된다.

 

DispatchQueue 에 workItem 처리 요청이 들어오면, 이 때 Thread 가 지정되어 실행하게 된다.

즉 DispatchQueue 를 백만개 만들더라도, Thread 의 최대 개수는 OS 에 의해서 제한되고,

각각의 DispatchQueue 와 Thread 스케줄 역시 OS가 알아서 해준다.

- 사실 DispatchQueue 의 최대 개수가 제한되어 있다고 알려져 있다.