DispatchQueue

도윤·2022년 8월 12일
0

iOS

목록 보기
5/11

아래 URL의 내용을 참고하여 작성한 게시글입니다.

https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation

https://caution-dev.github.io/ios/2019/03/15/iOS-GCD-vs-Operation-Queue.html

Grand Central Dispatch

기본적으로 스레드 풀의 관리를 프로그래머가 아닌 운영체제에서 관리하기 때문에 프로그래머가 작업을 비동기적으로 간편하게 사용할 수 있습니다. 프로그래머가 작업을 생성하고 Dispatch Queue에 추가하면 GCD는 작업에 맞는 스레드를 자동으로 생성해서 실행하고 작업이 종료되면 스레드를 제거합니다.

Dispatch Queue

작업을 담고 있는 대기열로, 항상 선입선출(FIFO : First in first out) 방식으로 실행됩니다. 대기열에 추가된 항목은 시스템이 관리하는 스레드 풀에서 처리하고 작업을 완료하면 스레드를 알아서 해제합니다.

  • Serial Disptach Queue : 한 번에 하나의 작업만을 실행하며 이 작업이 끝나서 대기열에서 제외도고 새로운 작업이 시작되기 전까지 기다립니다. 별도로 명시하지 않으면 DispatchQueue의 기본은 Serial 입니다.
  • Concurrent Disptach Queue : 이미 시작된 작업이 완료될 때까지 기다리지 않고 가능한 많은 작업을 진행합니다. 즉, 병렬처리 방식입니다.

Main Queue

메인 스레드에서 처리되는 serial queue이고 모든 UI작업은 main thread에서 시행한다.

Serial Queue로 단일 스레드이다.

DistpachQueue.main.sync

DispatchQueue.main.sync{ //code }

메인 Thread에서 코드를 순차 실행하는 명령어이지만 앱의 UI를 메인스레드에서 담당하기 때문에 main.sync를 호출하게 되면 앱의 이벤트나 UI처리가 멈추게 된다.

정확히는 데드락을 유발한다.

메인스레드에서 작업중인 블락을 MainQueue로 sync로 넘기게 되면 메인 스레드는 mainQueue에 넘긴 해당 블락이 끝나기만을 기다리게 된다.

GlobalQueue에서 비동기로 이미지를 불러왔을 때 이미지를 보여주는 순서가 중요하게 된다면 Main.sync로 할당할 때 쓰인다.

DispatchQueue.main.async

DispatchQueue.main.async { // code }

해당 작업을 메인큐로 비동기적으로 보내는 개념이다.

많이 헷갈렸는데 현재 스레드에서 작업할 태스크를 메인큐로’ 비동기적으로 보내는 것이다. 메인 큐에서 작업 분배를 비동기적으로 한다고 이해했는데 잘못된것이다!

하지만 이부분에서 많은 의구심이 들었다. 만약 메인스레드에서 실행되는 작업을 DispatchQueue.main.async로 보내게 되면 결국엔 순서만 바꾸는 것이 아닌가..? 유의미안 의미가 있나? defer와 유사해 보인다..

유의미한 사용을 위해선 메인스레드가 아닌 스레드에서 작업을 마치고 메인스레드에 작업을 넘기고 다른 스레드는 계속 작업을 하도록 사용할때 가장 유용해보인다!

Global Queue 생성

DispatchQueue.global().sync {}
DispatchQueue.global(qos: .background).async {}

Quality of Service (QoS)

Concurrent하게 작업을 처리하면서 작업의 우선 순위를 지정하기 위해 사용합니다.

  • userInteractive : 유저와 직접적 인터렉티브, UI업데이트, 애니메이션, UI반응관련 어떤것이든(상호자의 상호 작용) 몇초
  • userInitiated: 유저가 즉시 필요하긴 하지만, 비동기적으로 처리된 작업(ex. 앱내에서 pdf파일을 여는 것과 같은)
  • default: 일반적인 작업
  • .utility: 보통 Progress Indicator와 함께 길게 실행되는 작업, 계산, IO, Networking, 지속적인 데이터 feeds (몇초에서 몇분)
  • .background: 유저가 직접적으로 인지하지 않고(시간이 안중요한) 작업, 데이터 미리 가져오기, Networking, 지속적인 feeds (속도보다는 에너지 효율성 중시, 몇분 이상)

우선순위에 따른 큐에서의 작동방식

→ iOS가 알아서 우선적으로 중요한 일임을 인지하고 쓰레드에 우선순위를 매겨 더 여러개의 쓰레드를 배치하고 배터리를 더 집중하게 해서 사용하도록 함.

let queue = DispatchQueue.global(qos: .background)

queue.async(qos: .utility) {
	//task
}

다음과 같이 큐에 부여한 우선순위와 다른 동작을 할때 qos를 바꾸면 queue의 우선순위 또한 바뀌게 된다고 한다!

URLSession 따로 호출할 때 다른스레드로 안 옮기고 .background로 작동하고 있다고 합니다. 만약 우선순위를 지정하고 싶다면 아래의 코드처럼 따로 OperationQueue를 이용하여 큐를 직접 정해줘도 된다고 한다.

URLSession.init(configuration: **<#T##URLSessionConfiguration#>**, delegate: **<#T##URLSessionDelegate?#>**, delegateQueue: **<#T##OperationQueue?#>**)

Custom Dispatch Queue 생성

큐를 커스텀으로 만들 수 있다. 매개변수에는 label, qos, attributes가 있다.

아무값도 넣지 않으면 default값은 serial이다. ⇒ serial, concurrent로 둘 다 설정가능.

Dispatch Queue(label: "com.serialQueue").async {}
Dispatch Queue(label: "com.concurrentQueue",
              qos: .default,
              attributes: .concurrent,
              autoreleaseFrequency: .inherit,
              target: nil).async {}

asyncAfter : 지정된 시간이 지나면 작업을 실행합니다.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {} DispatchQueue.main.asyncAfter(deadline: .now() + .miliseconds(100)) {}

Serial, Concurrent, sync, async

Serial과 Concurrent의 차이는 직렬 Queue와 병렬 Queue의 차이를 가집니다.

  • sync : 큐에 작업을 추가한 후, 추가된 작업이 종료될 때 까지 기다립니다. sync의 경우 하나의 작업이 Queue에서 빠져나갈 때까지 기다리기 때문에, Serial 이냐 Concurrent냐의 차이는 없습니다. sync로 모든 작업을 Queue에 넣을 경우 그 순서가 보장됩니다.

Concurrent * Async 방식에서는 어떨까요?

let fisrtConcurrentQueue = DispatchQueue(label: "first",
                                   qos: .default,
                                   attributes: .concurrent,
                                   autoreleaseFrequency: .inherit,
                                   target: nil)
let secondConcurrentQueue = DispatchQueue(label: "second",
                                   qos: .default,
                                   attributes: .concurrent,
                                   autoreleaseFrequency: .inherit,
                                   target: nil)
fisrtConcurrentQueue.async { print("1") }
secondConcurrentQueue.async { print("2") }
fisrtConcurrentQueue.async { print("3") }
secondConcurrentQueue.async { print("4") }
fisrtConcurrentQueue.async { print("5") }
secondConcurrentQueue.async { print("6") }

==> 결과
1
4
5
6
3
2

병렬 큐에 비동기로 동작되기 때문에 작업 처리의 순서가 보장되지 않습니다.

주의사항

UI관련 작업은 모두 Main Queue에서만 담당해야한다.

다른 Queue에서 작업하다가도, 화면에 표시해야 하는 작업은 반드시 MainQueue로 보내서 작업해야 함을 주의하자!!!

DispatchQueue.global().async {
// (비동기 네트워크 작업) 이미지 다운로드...

			DispatchQueue.main.async {
			// (UI관련) 다운로드한 이미지를 화면에 표시
	    {

}

주의사항2

플레이그라운드에서는 mainQueue를 사용하지 않는다. GlobalQueue가 default로 동작한다.

주의사항3

메인 스레드에서 다른 큐로 보낼 때 sync를 사용하면 안된다.

DispatchQueue.main.async {
			DispatchQueue.global().sync{
						//task
			}
}

다른 쓰레드에서 작업 하는 이유는 메인에서 동작하는 작업(UI관련 등)과 동시적으로 수행하여 속도를 향상시키기 위함인데,sync를 사용하면 해당 작업이 끝날때까지 메인 쓰레드를 block 시키기 때문에 앱이 버벅이게 된다.

주의사항4

현재 큐에서 같은 큐로 sync를 사용하면 안된다.

DispatchQueue.global(). async {
			DispatchQueue.global().sync{
						//task
			}
}

실행되고 있던 쓰레드에서 현재 큐로 sync로 작업을 보내면 실행중이던 쓰레드는 block 상태가 된다.

다시 현재 큐에 들어온 작업을 쓰레드에 배치하는 과정에서 block 상태의 쓰레드에 배치된다면 교착상태가 발생한다.

global큐의 경우 항상 교착상태가 발생하는 것은 아니다. 만약 2번스레드에서 sync로 Global큐에 보냈을 때, global은 concurrent queue이므로 2번 외에 다른 스레드에 작업을 배치할 수도 있다. 이런 경우에는 교착상태가 발생하지 않는다.

하지만 교착상태가 발생할 가능성을 내포하고 있으므로 사용을 지양해야된다

주의사항5

작업을 보낸다는 것은 클로저를 보낸다는 것이므로 캡처 현상이 발생할 수 있다.

따라서 객체 내부에서 비동기 globalQueue를 사용할 경우, weak self 선언을 해주지 않으면 strong 참조를 하게 되어

작업 도중 객체가 사라지게 되어도 ARC가 남아있어 여전히 동작하게 된다. (strong capture현상)

따라서 weak 선언을 통해 객체가 사라질 때 작업도 자연스럽게 종료되도록 해야 한다.

Completion Handler

비동기 작업의 끝나는 시점을 알리고자 할 때 사용한다.

→ 비동기 작업을 사용하는 메소드는 completion Handler를 사용해서 값을 반환한다. return X!

0개의 댓글