[Swift] GCD & Dispatch Queue
프로그램은 실행되며 여러가지 작업을 수행해야 하는데, 멀티스레드 프로그래밍을 이용하지 않고 하나의 스레드에서 작업을 모두 수행한다면 당연히 많은 시간이 걸릴것입니다.
iOS 의 메인 스레드는 UI 를 화면에 그려주는 스레드입니다. 이 경우 화면에 UI 를 그려줘야 하는데, 단순 연산이나 네트워크 요청 등 다른 작업들을 모두 메인 스레드에서 실행한다면 앱이 버벅거릴수도 있을 것입니다.
GCD (Grand Central Dispatch)
그래서 다른 스레드에서 여러 작업을 수행할 수 있도록 적절하게 분배해주는 과정이 필요합니다. 이는 동시성 프로그래밍이라고 불립니다.
GCD 는 멀티코어와 멀티프로세싱 환경에서 최적화된 프로그래밍을 할 수 있도록 해줍니다.
Java 나 C 같은 언어는 명시적으로 스레드를 만들고 작업도 특정하게 지정해주어야 하지만, iOS 에서는 이를 개발자가 아닌 OS 에서 관리하기 때문에 태스크를 비동기적으로 쉽게 사용할 수 있습니다.
개발자가 실행할 태스크를 생성하고 Dispatch Queue 에 추가하면 GCD 는 태스크에 맞는 스레드를 자동으로 생성하여 실행하고, 작업이 종료되면 해당 스레드를 제거합니다.
Dispatch Queue
Dispatch Queue 는 작업을 연속적 혹은 동시에 진행하기는 하지만, Queue 라는 이름답게 언제나 먼저 들어오면 먼저 나가는 순서로 실행 됩니다.
Dispatch Queue 에는 2가지 종류가 존재합니다.
Serial Dispatch Queue
Serial Dispatch Queue (직렬) 는 등록된 태스크를 한번에 하나씩 순차적으로 처리합니다.
처리중인 태스크가 완료되어야 다음 태스크를 처리할 수 있습니다.
Concurrent Dispatch Queue
Concurrent Dispatch Queue (동시) 는 시작된 태스크를 기다리지 않고 가능한 많은 작업을 진행합니다.
Dispatch Queue 와 GCD 가 같은 개념이라고 착각할 수 있지만, Dispatch Queue 는 GCD 기술의 일부로 다른 개념입니다.
저희는 Dispatch Queue 를 사용할때, 3종류의 큐를 선택하여 사용할 수 있습니다.
Main Queue - 메인 스레드에서 처리되는 Serial Queue, UI 관련 태스크 담당
Global Queue - 전체 시스템에서 공유되는 Concurrent Queue, QoS 우선순위로 구분해 사용
Custom Queue - 사용자가 어떤 큐를 생성할지 결정 가능, 기본값은 Serial, Main Queue 와 구분이 필요할 때 사용
QoS 는 Quality of Service 로 Global Queue 에서 Concurrent 한 작업을 처리할 때 이를 이용해 우선순위를 정할 수 있으며, 이는 아래와 같습니다.
동기와 비동기 (Sync & Async)
저희는 수행해야 하는 태스크를 Dispatch Queue 로 보내주었습니다. 이후 메인 스레드에서는 큐로 보낸 태스크가 완료되기까지 기다리거나, 혹은 바로 다음 태스크를 수행할 수 있습니다.
이를 동기와 비동기라고 부릅니다.
비동기 (Async)
큐로 보낸 태스크의 완료 여부에 상관없이 바로 다음 태스크를 수행하는 것을 비동기라고 합니다.
코드로 예를 들자면 위와 같은 식으로 사용할 수 있는데, 이를 해석하자면 원래 태스크가 실행되던 메인 스레드에서 Global Queue 로 태스크를 보낸 후, 해당 태스크의 완료 여부와 상관없이 바로 다음 태스크를 진행한다 입니다.
위와 같은 코드를 예시로 들어보겠습니다.
각각 1, 2, 3초가 걸리는 태스크를 Global Queue 로 보냈는데, 이 코드는 태스크의 완료여부 즉 수행시간과 상관 없이 다음 태스크를 진행하기 때문에 0.1 초도 걸리지 않는 짧은 시간안에 모두 완료됩니다.
태스크를 비동기로 실행되도록 큐에 보낸 후, 해당 태스크가 끝나는 시점을 알아야 하는 경우가 있는데, 이는 completionHandler 라는 클로저를 통해 알 수 있습니다.
동기 (Sync)
동기는 비동기와 반대로 큐에 보낸 태스크들이 완료될 때까지 기다린 후 다음 태스크를 실행하는 것을 의미합니다.
따라서 위의 코드는 순서대로 태스크를 실행하며 각 태스크가 완료될때까지 기다린 후 실행되기 때문에 6초가 넘는 시간이 걸립니다.
하지만 메인 스레드에서 큐로 동기적으로 실행되는 태스크를 보낸다면 실질적으로는 비슷한 시간이 걸리기 때문에 큰 효과가 없습니다.
비동기로 태스크를 처리하는 이유는 시간을 절약하기 위해서인데, 시간이 많이 드는 태스크는 대부분 네트워크 통신이기 때문에 이와 관련된 태스크는 비동기적으로 구현되어 있다고 합니다.
Sync & Async != Serial & Concurrent
그렇다면 순차적으로 태스크를 진행하는 동기와 Serial 은 같은 개념일까요? 결론부터 말하자면 아닙니다.
Sync & Async 는 태스크를 큐로 보내는 시점에서 태스크의 완료를 기다릴지 말지에 대해 결정하는 것이고,
Serial & Concurrent 는 큐로 보내진 태스크들을 순차적으로 실행할지 동시에 실행할지에 대해 결정하는 것입니다.
그럼 각각의 조합에 대해 알아보겠습니다.
Serial - Sync
메인 스레드는 큐로 보낸 태스크가 완료될때까지 기다리며 (Sync)
큐로 보내진 태스크는 순차적으로 실행되기 때문에 이전 태스크가 끝나길 기다립니다. (Serial)
Concurrent - Sync
메인 스레드는 큐로 보낸 태스크가 완료될때까지 기다리며 (Sync)
큐로 보내진 태스크는 이전 태스크와 상관없이 다른 스레드에 보내지며 동시에 실행됩니다. (Concurrent)
Serial - Async
메인 스레드는 큐로 보낸 태스크의 완료 여부에 상관없이 다음 태스크를 실행하고 (Async)
큐로 보내진 태스크는 순차적으로 실행되기 때문에 이전 태스크가 끝나길 기다립니다. (Serial)
Concurrent - Async
메인 스레드는 큐로 보낸 태스크의 완료 여부에 상관없이 다음 태스크를 실행하고 (Async)
큐로 보내진 태스크는 이전 태스크와 상관없이 다른 스레드에 보내지며 동시에 실행됩니다. (Concurrent)
하지만 이런 조합을 마구잡이로 사용해서는 안되며, 주의해야할 순간들이 있습니다.
Main Queue - Sync
Main Queue 는 Serial 하게 태스크를 수행하는데,
Sync 를 사용한다면 메인 스레드는 그대로 진행을 멈추고 태스크가 끝날때까지 기다리게 됩니다.
그리고 큐에 추가된 태스크는 메인 스레드로 할당되는데
이미 메인 스레드는 멈춰있는 상태이기 때문에 태스크를 실행할 수 없습니다.
이렇게 되면 큐에 등록된 태스크가 시작도, 실행도, 종료도 되지 않는 Dead Lock 상태에 빠지게 됩니다.
같은 Queue 에서의 Sync
같은 Queue 에서 태스크를 등록하고 해당 태스크를 동일 Queue 에 Sync 로 등록하게 되면
멈춰있는 스레드에 다시 태스크를 할당하는 일이 발생할 수 있습니다.
이렇게 되면 메인 스레드의 경우와 비슷하게 Dead Lock 상태에 빠지게 됩니다.
마무리 & 참고 링크
오늘은 GCD 와 Dispatch Queue 에 대해 알아보았습니다.
처음 학습하는 내용이기도 하고, 적극적으로 사용해본 적이 없어 잘못된 정보가 있을 수 있으니 발견하신다면 댓글로 남겨주세요!
감사합니다.
https://www.boostcourse.org/mo326/lecture/16916?isDesc=false
https://magi82.github.io/gcd-01/
https://jeonyeohun.tistory.com/279
https://www.youtube.com/watch?v=EL5n2U8XNpQ&list=PLqK3bFbiW77MBn0ofPRjGDBU9Ytnz9-6S&index=1
'iOS 개발 > Swift' 카테고리의 다른 글
[TIL] Swift 에서 Array 를 탐색하는 방법들의 차이 (for in) (0) | 2022.07.17 |
---|---|
[Swift] Optional (0) | 2022.07.04 |
[Swift] 싱글톤 패턴 (Singleton Pattern) (0) | 2022.05.20 |
[Swift] 타입 캐스팅 (Type Casting) (0) | 2022.05.19 |
[Swift] 프로토콜 (Protocols) (0) | 2022.05.13 |