동기와 비동기
동기(sync)
- 다른 쓰레드로 작업을 보내고, 그 작업이 끝날 때까지 기다린다.
비동기(aysnc)
- 다른 쓰레드로 작업을 보내고, 작업이 끝나는 것을 기다리지 않고 다른 작업을 시작한다.
직렬과 동시
직렬
- 하나의 쓰레드에서만 작업을 처리한다.
- 순서가 중요한 작업을 처리할 때 사용
동시
- 몇 개의 쓰레드를 사용하는지 알 수는 없지만, 여러 개의 쓰레드에서 작업을 처리한다.
- 각자 독립적이지만 유사한 여러개의 작업을 처리할 때 사용
동시성 처리
- 직접적으로 쓰레드를 관리하는 개념이 아닌, 대기열(Queue)의 개념을 이용해서, 작업을 분산처리하고, OS에서 알아서 쓰레드 숫자(갯수)를 관리
- 메인쓰레드가 아닌 다른 쓰레드에서 오래 걸리는 작업(예: 네트워크 처리)들과 같은 작업들이 쉽게 비동기적으로 동작하도록 함
- 성능 / 반응성 / 최적화와 관련 (화면의 버벅 거림의 문제를 해결하기 위한 프로그래밍 기법)
병렬과 동시
동시
- 실제 1개의 쓰레드(물리적)가 OS영역에서 여러개의 쓰레드로 나뉠 수 있다. 나뉘어진 여러개의 쓰레드(소프트웨어적)는 앱 단위에서 각기 다른 역할로 쓰일 수 있다.
- 실제 1개의 물리적 쓰레드가 여러개의 소프트웨어적 쓰레드로 나뉘어 여러가지의 작업을 처리하는 것 (소프트웨어적 쓰레드 관점)
병렬
- 실제로 물리적인 쓰레드가 여러개 있고, 여러개의 쓰레드가 각자의 작업을 하는 것 (물리적인 쓰레드 관점)
Queue(대기열)의 종류
-
DispatchQueue (GCD - Grand Central DispatchQueue)
-
OperationQueue
큐의 종류 | | 생성 코드 | 특징 | 직렬/동시 |
---|
| .main | DispatchQueue.main | 메인큐 (UI업데이트 내용 처리하는 큐) | Serial (직렬) |
Dispatch Queue (GCD) | .global() | DispatchQueue.global(qos: ) | 6가지 Qos(작업에 따라 Qos상승가능) (시스템이 우선순위에 따라 더 많은 쓰데드를 배치하고, 배터리를 집중해서 사용하도록 함) | Concurrent (동시) |
| custom (프라이빗) | DispatchQueue(label: "...") | Qos추론/Qos설정가능 | 디폴트: Serial 둘 다 가능 (attributes로 설정) |
OperationQueue | | let opQ = OperationQueue() | 디폴트: .background 기반(underlying) 디스패치큐에 영향 받음 (unspecified)를 제외한 5가지) | 디폴트: Concurrent 둘 다 가능 (maxConcurrentOperationCount로 사용할 쓰레드 갯수 설정 가능) |
큐의 Qos (서비스품질)
- userInteractive
- 유저와 직접적 인터렉티브: UI 업데이트 관련(직접X), 애니메이션, UI반응관련 어떤 것이든
(사용자와 직접 상호 작용하는 작업에 권장, 작업이 빨리 처리되지 않으면 상황이 멈춘 것처럼 보일만한)
- 소요시간: 거의 즉시
- userInitiated
- 유저가 즉시 필요하긴 하지만, 비동기적으로 처리된 작업
(ex. 앱내에서 pdf 파일을 여는 것과 같은, 로컬 데이터베이스 읽기)
- 소요시간: 몇초
- default
- utility
- 보통 Progress Indicator와 함께 길게 실행되는 작업, 계산
(ex. Networking, 지속적인 데이터 feeds)
- 소요시간: 몇초에서 몇분
- background
- 유저가 직접적으로 인지하지 않고(시간이 안 중요한)작업
(ex. 데이터 미리가져오기, 데이터베이스 유지보수, 원격 서버 동기화 및 백업 수행)
- 소요시간: 몇분이상 (속도보다는 에너지효율성 중시)
- unspecified
- legacy API 지원
(스레드를 서비스 품질에서 제외시키는)
GCD 사용 시 주의사항
- 반드시 메인큐에서 처리해야하는 작업
에러 발생
: UI와 관련된 작업들은 메인 쓰레드에서 처리하지 않으면 에러가 발생!
메인쓰레드
: UI와 관련된 작업들을 메인쓰레드에서 처리할 수 있도록 메인큐를 통해서, 작업을 다시 메인쓰레드로 보냄
DispatchQueue.global(qos: .utility).async{
...
DispatchQueue.main.async{
self.textLabel.text = "New posts updated!"
}
}
- 컴플리션핸들러의 존재이유 - 올바른 콜백함수의 사용
잘못된 함수설계
: 비동기적인 작업을 해야하는 함수를 설계할 때 return을 통해서 데이터를 전달하려면 항상 nil이 반환
제대로된 함수설계
: 비동기적인 작업을 해야하는 함수는 항상 클로저를 호출할 수 있도록 함수를 설계해야 함
func getImages(..., c...: @escaping (UIImage?) -> Void) {
...
URLSession.shared.dataTask(..){
...
completion(photoImage)
}.resume()
}
- weak, strong 캡처의 주의 - 객체 내에서 비동기코드 사용 시
강한참조
: 캡처리스트 안에서 weak self 로 선언하지 않으면 강한 참조(strong)
1) 서로를 가리키는 경우 메모리 누수(Memory Leak)발생 가능
2) (메모리 누수가 발생하지 않아도) 클로저의 수명주기가 길어지는 현상이 발생할 수 있음
약한참조
: 대부분의 경우 캡처리스트 안에서 weak self로 선언하는 것을 권장
DispatchQueue.global(qos: utility).async{ [weak self] in
guard let 'self' = self else {return}
...
DispatchQueue.main.async{
self.textLabel.text = "New posts update!"
}
}
- 동기함수를 비동기적으로 동작하는 함수로 변형하는 방법
일반(동기) 함수
: 오래 걸리는 일반적인 함수를 단순히 동기함수로 만들면 메인쓰레드에 부하가 걸림
비동기함수로
: 오래걸리는 일반적인 함수를 내부에 비동기적 처리를 하면 비동기로 동작하는 함수로 변형 가능
func doSomething(com: @escaping (Void) -> Void) {
DispatchQueue.global().async{
...
}
}
비동기적으로 구현된 메서드
- URLSession.shared.dataTask
- 이미 내부적으로 GCD를 이용해서, 비동기적으로 처리하는 메서드로 생각해야 됨
동시성 프로그래밍과 관련된 문제점
경쟁상황 / 경쟁조건 (Race Condition)
- 멀티 쓰레드의 환경에서, 같은 시점에 여러개의 쓰레드에서 하나의 메모리에 동시접근 하는 문제
- (메모리에) 쓰고 있는 동안에는 여러 쓰레드에서 접근하지 못하도록 Lock(잠금) -> Thread-Safe 처리
DeadLock
- 멀티 쓰레드의 환경에서, 배타저인 메모리사용으로 일이 진행이 안되는 문제
- 2개 이상의 쓰레드가 서로 배타적인 메모리의 사용으로 인해 (서로 잠그고 점유하려고 하면서) 메서드의 작업이 종료도 못하고 일의 진행이 멈춰버리는 상태
Thread Safe 처리
- 다른 쓰레드에서 동시에 접근하지 못하도록 하는 여러가지 기법
동시큐에서 직렬큐로 보내기!!
직렬큐는 하나의 쓰레드에서만 한 번에 메모리에 접근하기 때문에 동시큐로 처리하는 중에 시리얼큐로 보낸다!