하지만, 스레드와 동일한 개념은 아니다.
일반적인 스레드의 설명을 먼저하자면
스레드는 크게 두가지로 나뉜다.
- 직렬 처리 스레드
- 병렬 처리 스레드
웹 Api를 리퀘스트A와 리스폰스B가 있다고 해보자
리퀘스트A와 리스폰스B는 직렬 처리이다.
만약 리퀘스트 A가 여러번 발생할 수 있다면 이것은 병렬처리로 해야한다.
즉, 하나의 처리는 대부분 직렬처리와 병렬처리가 병행된다.
일반적으로 하나의 큰 직렬처리 안에 여러개의 병렬처리가 쪼개져 있다.
GCD에서의 queue
- main: app의 main thread에서 동작 병렬 Queue
- global: 전체 시스템에서 공유하는 병렬 Queue
- custom: 개발자가 만들 수 있음.
iOS에서 UI의 변경 작업은 반드시 main 쓰레드에서 동작한다. (안드로이드도 동일)
또한 한번에 여러가지 UI이벤트가 있을 수 있으므로 동일하게 병렬 처리 큐이다.
또한 UX의 이유로 main thread는 정지하면 안된다.
UI animation 처리가 아닌 연산 처리로 main thread를 점유하면 안된다.
GCD의 큐에는 총 4가지의 QoS(Quality of Service: 우선 순위)가 존재한다.
- userinterative: 즉각적인 반응이 필요한 queue. main 쓰레드가 이 QoS를 사용한다.
- userinitiated: 유저의 UI 중 중요도가 떨어지는 element를 async로 작업할때 쓴다.
- utility: 유저의 프로세스에 필요한 연산작업에 사용한다. 프로그래스바, I/O, Networking 등
- background: 유저에게 직접적으로 필요하지 않은 작업들. logging 등
병렬처리에 있어서 주의 해야할 것들
공유자원
상호배제
데드락
starvation
우선순위 역전
멀티 스레딩에 있어서 가장 걱정해야 하는 것은 자원의 공유이다.
하나의 자원이 공유되는데 이 자원이 비동기(async) 병렬 작업으로 여러곳에서 순서없이 동시에 접근하여 데이터가 변경되는데 이 변경된 데이터를 다른곳에서 참조한다고 할때 데이터의 정합성을 보증할 수 없다. 이 것을 위해 세마포어(Semaphore) 와 뮤텍스(Mutex)라는 개념이 있다.
Semaphore(세마포어) - 원래뜻은 기차등에서 사용하는 까치발 신호기
공유된 자원데이터를 여러 `프로세스`가 접근하는 것을 막음
자원데이터를 카운팅하여 몇가지의 쓰레드가 접근 할 수 있는지 확인할 수 있음
지정한 수 이상 쓰레드가 접근하면 대기하도록 함.
Mutex(Mutual Exclusion: 상호 배제)
공유된 자원 데이터를 여러 `쓰레드`가 접근하는 것을 막음
즉, 멀티쓰레드중 한가지의 쓰레드만 사용가능한 리소스
1과 0만을 갖고 있는, 즉 한계치가 1인 Binary Semaphore(이진 세마포어)
RxSwift에 있어서 스케쥴러 클래스
ConcurrentDispatchQueueScheduler
큐의 종류와 QoS를 지정할 수 있는 스케쥴러
background에서 작업할때 사용한다.
ConcurrentMainScheduler
메인스레드를 사용하는 스케쥴러
만약이 스케쥴러가 메인스레드에서 호출되면 별도의 스케쥴링 없이 바로 실행된다.
이 스케쥴러는 SubscribeOn에 최적화되어 있으므로 ObserveOn을 할 경우에는 MainScheduler를 사용할 것.
CurrentThreadScheduler
현재 스레드에서 실행되는 스케줄러.
기본 오퍼레이터는 이 스케줄러를 사용한다.
VirtualTimeScheduler
가상의 시간을 만들어내고 그 위에 스케쥴러를 돌린다.
HistoricalScheduler
절대시간(Date)이나 상대시간(TimeInterval)을 이용하여 VirtualTimeScheduler를 사용하는 클래스
MainScheduler
DispatchQueue.main에서 작동하는 스케쥴러.
이 스케쥴러가 DispatchQueue.main에서 사용되면 별도의 스케쥴링 없이 바로 실행된다.
UI작업에서 주로 사용된다.
SerialDispatchQueueScheduler가 특화된 클래스.
ObserveOn에 특화된 스케줄러
SubscribeOn을 할 경우에는 ConcurrentMainScheduler을 사용할 것.
OperationQueueScheduler
특정 OperationQueue에서 작업하는 스케줄러
maxConcurrentOperationCount을 사용하여 대규모 작업을 백그라운드에서 실행할 경우 사용
SerialDispatchQueueScheduler
특정 큐를 지정할 수 있는 스케줄러.
단, 병렬 큐를 지정해도 직렬로 처리하므로 주의.
사용하기전에 serialQueueConfiguration를 통해 몇가지 설정이 가능함
* thread-safe
쓰레드 자체를 안전하게 사용하는지에 관한 이야기.
iOS에서는 GCD(Grand Central Dispatch)에 의해서 자동적으로 thread-safe가 된다.
thread-safe의 조건
1. Re-entrancy
어떤 함수가 한 스레드에 의해 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하더라도 그 결과가 각각에게 올바로 주어져야 한다.
2. Thread-local storage
공유 자원의 사용을 최대한 줄여 각각의 스레드에서만 접근 가능한 저장소들을 사용함으로써 동시 접근을 막는다.
이 방식은 동기화 방법과 관련되어 있고, 또한 공유상태를 피할 수 없을 때 사용하는 방식이다.
3. Mutual exclusion
공유 자원을 꼭 사용해야 할 경우 해당 자원의 접근을 세마포어 등의 락으로 통제한다.
4. Atomic operations
공유 자원에 접근할 때 원자 연산을 이용하거나 '원자적'으로 정의된 접근 방법을 사용함으로써 상호 배제를 구현할 수 있다.