원문
Difference between DispatchQueue.main.async and DispatchQueue.main.sync
뜻을 이해하기 쉽도록 의역/추가설명을 붙인 부분들이 있습니다.
Question
저는 그동안 UI 관련 동작을 수행하기 위해 DispatchQueue.main.async 를 계속 사용해왔습니다.
Swift는 DispatchQueue.main.async 와 DispatchQueue.main.sync 를 제공해주며 둘 다 main queue 에서 수행됩니다.
두 개의 차이가 무엇인지, 언제 무엇을 사용해야 하는지 알려주실 분이 계신가요?
DispatchQueue.main.async {
self.imageView.image = imageView
self.lbltitle.text = ""
}
DispatchQueue.main.sync {
self.imageView.image = imageView
self.lbltitle.text = ""
}
Answer
왜 동시성이 필요할까?
데이터 로딩 같은 무거운 작업을 실행하는 순간 어플리케이션의 UI는 느려지거나 멈추게 됩니다. 동시성(Concurrency)은 2개 이상의 일(task)를 "동시에" 수행할 수 있도록 해줍니다. 동시성의 단점으로는 thread safety 이슈가 있습니다. 예를 들면 서로 다른 쓰레드에서 동일한 변수를 변경하려고 시도하거나, 이미 다른 쓰레드에 의해 블럭되어 있는 리소스에 접근하려 시도하는 등의 문제입니다.
우리가 알아야 할 몇 가지 추상적인 개념들이 있습니다.
- Queues
- 동기/비동기처리의 퍼포먼스
- Priorities
- 자주 발생하는 문제들
Queues
Queue 는 다음 두 가지 중 하나의 처리 방식을 가집니다.
- serial (순차적으로 처리)
- concurrent (동시에 처리)
또한 Queue 는 다음 두 가지 중 하나의 접근 범위를 가집니다.
- global
- private
(global serial queue, global concurrent queue, private serial queue, private concurrent queue 모두 가능하다는 말입니다.)
Serial queue 에서는 task 가 하나씩 수행되고 종료됩니다. 반면 concurrent queue 의 경우에는 여러 task 가 동시에 수행되며 그 중 어느 것이 먼저 종료될지 알 수 없습니다. 같은 task 그룹을 수행한다면 당연히 concurrent queue 보다 serial queue 에서 더 많은 시간이 걸릴 것입니다.
Private queue 를 직접 생성하여 사용할 수도 있고 (serial 과 concurrent 모두 가능) 이미 제공되는 global (system) queue 들을 사용할 수도 있습니다. Main queue 는 global queue 중 유일한 serial queue 입니다.
UI와 관련없는 무거운 작업들, 예를 들어 네트워크에서 데이터를 받아오는 등의 작업은 main queue 대신 다른 queue 에서 돌리는 것이 권장됩니다. UI가 뻗지 않고 계속 유저 액션을 받을 수 있도록 말입니다. 그러나 UI 업데이트를 다른 queue 에서 하게 되면 예상했던 것과 다른 타이밍/속도로 반영될 위험이 있습니다. 몇몇 UI 요소는 필요한 시점 이전이나 이후에 그려질 수도 있습니다. 이것은 UI의 붕괴를 초래합니다. Global queue 에 대해서도 염두에 두어야 할 사항이 있습니다. Global queue 는 system queue 이기 때문에 system 에 의한 다른 task 들이 돌아갈 수 있다는 사실입니다.
Quality of Service / Priority
Queue 에는 qos (Quality of Service) 를 설정할 수 있습니다. 이것에 따라 작업 수행에 대한 priority(우선순위)가 결정됩니다. 우선순위가 높은 것부터 나열해보았습니다.
.userInteractive - main queue
.userInitiated - 유저가 시작한 작업이며, 유저가 응답을 기다리고 있음
.utility - 다소 시간이 걸리며 즉각적인 응답이 필요하지 않은 경우 (ex: 데이터 처리 작업)
.background - 눈에 보이지 않는 부분의 작업이고 완료 시간이 중요하지 않음
그리고 물론 이런 qos도 있습니다.
.default - qos를 딱히 설정하지 않은 경우. 이 경우 .userInitiated 와 .utility 사이가 된다.
작업은 동기적으로(synchronously) 수행되거나 비동기적으로(asynchronously) 수행됩니다.
- Synchronous 함수는 작업이 다 끝난 다음에만 현재의 queue에게 컨트롤을 넘깁니다. 그 전까지 현재의 queue 는 block 되어 작업이 끝날 때까지 기다려야 합니다.
- Asynchronous 함수는 작업을 수행할 다른 큐에게 작업을 넘기자마자 현재의 queue에게 컨트롤을 돌려줍니다. 작업이 끝나기 전까지 기다릴 필요가 없으며 현재의 queue 도 block 되지 않습니다.
자주 발생하는 문제들
프로그래머가 가장 많이 겪는 동시성 문제에는 다음과 같은 것들이 있습니다.
- Race condition
- Priority inversion
- Deadlock
절대로 sync 함수를 main queue 에서 부르지 마십시오.
만약 main queue 에서 sync 함수를 부른다면 main queue 만이 아니라 작업이 완료되는 것을 기다리고 있는 queue 까지 block 될 것입니다. 그러나 작업은 절대로 끝나지 않습니다. Queue 가 이미 block 상태이기 때문에 시작조차 할 수 없기 때문입니다. 이것을 데드락이라고 합니다.
(역주: 설명이 친절하지 않아 덧붙입니다. 정확히 말하면, 메인 쓰레드에서 main queue + sync 를 부르지 마십시오. Sync 함수의 특성 상, main queue 에 넣은 task 가 완료되기 전까지 메인 쓰레드는 blocking waiting 상태가 됩니다. 그런데 이 ‘task’는 메인 스레드에서 serial 하게 실행이 되는 main queue에 들어가 있지요. 따라서 이미 block 상태로 빠진 메인 쓰레드에서 작업은 영영 시작되지 않습니다. 이렇게 무한대기 상태로 빠지기 때문에 데드락이라고 하는 것입니다.)
언제 sync 를 사용하는가? 작업이 끝나는 것을 기다려야 할 때 사용하세요. 예를 들면 어떤 함수/메소드가 중복으로 불리지 않는 것을 보장해야 할 때 사용하세요. 또 다른 예로는 하나의 동기화 작업을 진행하는 도중 이 동기화 작업을 중복으로 시도하지 않기 위해 사용하세요. 이것에 대한 코드는 여기서 볼 수 있습니다: How to find out what caused error crash report on IOS device?
'iOS 일반 > Stack Overflow' 카테고리의 다른 글
Swift의 Array가 멀티쓰레드에서 안전하지 않은데 어떻게 하면 될까요? (Stack Overflow) (0) | 2019.12.30 |
---|