GCD with QoS, Dispatch Queue

DevMinion·2022년 7월 12일
1

GCD란

GCD란 Grand Central Dispatch로 개발하며 자주 마주치는 Dispatch Queue와 관련이 있는 녀석이다.
단, GCD != Dispatch Queue이다. Dispatch Queue라는 녀석은 동시성 프로그래밍을 지원하는 Swift의 API로 GCD의 개념을 통해 만들어진 것이다.

CS를 공부하며 접한 여타 C나 자바같은 언어는 스레드를 직접 생성하고 해당 스레드에 해야할 작업도 할당하여 개발자의 책임이 크고 귀찮은 작업이었다. 운영체제 과제를 생각하면...
하지만 우리의 Swift는 해당 작업을 그냥 Dispatch Queue에게 던져주기만 하면 알아서 적절한 스레드에 할당하여 작업을 해준다. 이 얼마나 편한가.

Dispatch Queue

Serial, Concurrent

Serial과 Concurrent는 직렬과 병렬을 말한다.
아래 그림을 보면,

발그림 ㅈㅅ...

이렇게 큐에 담긴 각각의 Task1,2,3,4를 Serial은 한 쓰레드에 모두, Concurrent는 여러 쓰레드에 병렬로 할당한다.

여기서 Concurrent가 분배하는 쓰레드의 수는 시스템이 결정한다.

코드를 통해서도 확인할 수 있다.

  • Serial
var numbers1 = [0,1,2,3,4,5,6,7]
let dispatchQ1 = DispatchQueue(label:"serial") //Custom serial queue
(0..<8).forEach({ index in
    dispatchQ1.async {
        print(numbers1[index])
    }
})

먼저 Custom Queue를 생성해준다. Custom Queue는 뒤에 나오니 그러려니 하자.
해당 코드를 실행하면,

이처럼 순서가 보장된 출력을 확인할 수 있다. Serial을 사용하였기에 위의 그림처럼 한 쓰레드에 순서대로 작업을 할당하였기 때문이다. 그렇다면, Concurrent는 어떨까.

  • Concurrent
print("concurrent+async")
var numbers2 = [0,1,2,3,4,5,6,7]
let dispatchQ2 = DispatchQueue(label:"concurrent", attributes: .concurrent) //Custom concurrent queue
(0..<8).forEach({ index in
    dispatchQ2.async {
        print(numbers2[index])
    }
})


이처럼 순서가 보장되지 않는 출력을 확인할 수 있다(물론 concurrent에 sync를 사용하면 순서가 보장된다). 0부터 7까지의 작업에 대해서 종료에 상관 없이 여러 쓰레드에 작업을 할당 하였기에 먼저 끝나는 작업부터 출력되어 이렇게 순서가 다른것을 확인할 수 있다. 다시 실행하면 또 다른 결과를 출력한다.

Async, Sync

Async는 비동기, Sync는 동기를 뜻한다. 각각의 특성과 예제를 확인해보자.

Async

  • 등록한 작업의 완료를 기다리지 않는다.
  • 해당 작업이 끝나지 않아도 코드를 진행한다.
var arr = ["ㄱ","ㄴ","ㄷ","ㄹ","ㅁ"]
DispatchQueue.global().async {
    for i in arr {
        print(i)
    }
}

for i in 1...5 {
    print(i)
}

Async로 선언했기에 순서대로 출력이 되는 것이 아닌, 섞여서 출력될 것이다. 결과를 확인하면,

Sync

  • 등록한 작업의 완료를 기다린다.
  • 해당 작업이 끝나기 전까지 코드를 진행하지 않는다.
var arr = ["ㄱ","ㄴ","ㄷ","ㄹ","ㅁ"]
DispatchQueue.global().sync {
    for i in arr {
        print(i)
    }
}

for i in 1...5 {
    print(i)
}

앞선 Async와 반대로 Sync로 선언했기에 순서대로 출력이 될 것이다. 결과를 확인하면,

이로서 Async와 Sync를 알아보았다.

Main, Global + Custom

Dispatch Queue를 사용할 때, 총 세가지의 Queue를 선택할 수 있다.
Main Queue, Global Queue 그리고 Custom Queue.

Main Queue

메인 쓰레드에서 작업을 수행하는 큐를 말한다. 메인 쓰레드는 단 하나로 Main Queue도 단 하나만 존재할 수 있다.
Main Queue에서 중요한 것은, Serial특성을 가지고 있다는 것이다. 앞서 설명한대로 순차적으로 작업을 진행하며, 처리한다. 또, 메인 쓰레드만이 가지고 있는 가장 중요한 역할중 하나는 UI 업데이트이다.

Global Queue

메인 쓰레드가 아닌 다른 쓰레드에서 작업을 수행하는 큐를 말한다.
Global Queue에서 중요한 것은, Concurrent특성을 가지고 있다는 것이다. 앞서 설명한대로 병렬적으로 작업을 진행하며, 처리한다.

Diffrent between Main & Global Queue

Main Queue와 Global Queue의 차이를 조금 더 자세히 알아보자. Serial, Concurrent 이렇게 다른 특성을 가지고 있다는 차이 뿐만 아니라, 하단의 코드에서 Main Queue와 Global Queue의 선언을 살펴보면,

DispatchQueue.main.async {
    <#code#>
}

DispatchQueue.global().async {
    <#code#>
}

이렇게 Main의 경우 그냥 main이라 적어주지만, Global의 경우 global()로 적는것을 확인할 수 있다. Apple의 Document를 살펴보면,

Main Queue의 Apple Documentation
main의 경우 이렇게 Type Property이며,

Global Queue의 Apple Documentation
global의 경우 Type Method이고 main Queue와는 다르게 파라미터로 qos를 받는 것을 확인할 수 있다. qos는 뒤에서 자세히 알아본다.

Custom Queue

Custom Queue는 앞선 Main, Global과는 다르게 사용자가 원하는 특성(serial, concurrent)을 선택하여 Dispatch Queue를 생성하는 것이 가능하다. 기본적으로는 Serial값을 가지며, attribute 인자를 통해 concurrent로 변경하여 사용하는 것이 가능하다.

// attributes가 없다면 기본적으로 serial
let customSerialDispatchQueue = DispatchQueue(label: "serial")

// attributes로 concurrent를 추가하면 concurrent
let customConcurrentDispatchQueue = DispatchQueue(label: "concurrent",attributes: .concurrent)

Serial, Concurrent + Async, Sync 조합

앞서 설명한 Serial, Concurrent와 Async, Sync를 각각 조합할 수 있다.

  • Serial + Async
  • Serial + Sync
  • Concurrent + Async
  • Concurrent + Sync

이렇게 4가지의 경우의 수가 존재한다. 하나씩 살펴보자.

  • Serial + Async
    • 하나의 쓰레드에 순차적으로 작업을 등록하며, 작업이 끝나는 것을 기다리지 않는다. 하지만 하나의 쓰레드에 순차적으로 작업을 등록하기에 작업의 등록 순서대로 출력 순서가 같다.
print("Serial + Async")
var numbers = [0,1,2,3,4,5,6,7]
let dispatchQ = DispatchQueue(label:"serial") //Custom serial queue
(0..<8).forEach({ index in
    dispatchQ.async {
        print(numbers[index])
    }
})
  • Serial + Sync
    • 하나의 쓰레드에 순차적으로 작업을 등록하며, 작업이 끝나는 것을 기다린다. 작업의 등록 순서대로 출력 순서가 같다.
print("Serial+Sync")
var numbers = [0,1,2,3,4,5,6,7]
let dispatchQ = DispatchQueue(label:"serial")
(0..<8).forEach({ index in
    dispatchQ.sync {
        print(numbers[index])
    }
})
  • Concurrent + Async
    • 여러개의 쓰레드에 작업을 등록하며, 작업이 끝나는 것을 기다리지 않는다. 작업의 등록 순서대로 출력 순서가 같지 않다. 먼저 끝나는 작업이 있다면 순서가 다를 수 있다.
print("Concurrent+Async")
var numbers = [0,1,2,3,4,5,6,7]
let dispatchQ = DispatchQueue(label:"concurrent", attributes: .concurrent) //Custom concurrent queue
(0..<8).forEach({ index in
    dispatchQ.async {
        print(numbers[index])
    }
})
  • Concurrent + Sync
    • 여러개의 쓰레드에 작업을 등록하며, 작업이 끝나는 것을 기다린다. Concurrent 특성을 가지고 있지만, 작업을 Sync하게 등록하기에 작업의 등록 순서와 출력 순서가 같다.
print("concurrent+sync")
var numbers = [0,1,2,3,4,5,6,7]
let dispatchQ = DispatchQueue(label:"concurrent", attributes: .concurrent)
(0..<8).forEach({ index in
    dispatchQ.sync {
        print(numbers[index])
    }
})

QoS(Quality of Service)

모든 작업에는 우선순위가 존재한다. 예를 들어 나에게는 게임하기 > 밥먹기 == 잠자기 이런 느낌? 앱에서는 UI업데이트 > 백그라운드 작업 이렇게 priority가 있을 것이다. 어떤 작업에 대해 priority를 정해줄 때, 우리는 QoS를 사용한다.
QoS에는 4(QoS)+2(Special QoS) 총 6가지의 분류가 존재한다.
QoS: Apple Documantation

  • userInteractive
  • userInitiated
  • default
  • utility
  • background
  • unspecified

QoS Class

먼저 일반적인 QoS부터 살펴보자.

userInteractive

가장 높은 우선순위를 가진다. Apple의 document에 따르면 userInteractive는 애니메이션, 이벤트 핸들링 그리고 UI 업데이트등의 주요한 작업이다.

userInitiated

사용자가 행하는 것에 대한 결과를 바로 보여줘야하는 작업, 사용자가 앱을 사용하는 것을 순간적으로 막을지 모르는 작업이다. API를 통한 데이터 로드등의 작업이다.

utility

사용자가 앱을 사용하는 것을 막지 않는 작업이다. 무언가 오래 걸리며 사용자와 상호작용을 하지 않는 작업이다.

background

가장 낮은 우선순위를 가진다. 말 그대로 앱이 백그라운드에서 실행중일 때 진행하는 작업이다.

Special QoS Class

밑으론 기본 QoS클래스 외에 두 개의 special한 QoS에 대한 설명이다.

defalut

QoS를 따로 지정해주지 않는다면 자동으로 defalut Class를 갖는다. 우리가 사용하는 Global Queue는 이 default에서 실행된다.

unspecified

QoS의 정보가 없음을 나타낸다. Apple의 document에 따르면,

The absence of a quality-of-service class.

라고한다.

정리

이로서 GCD와 DispatchQueue에 대해서 간략하게 알아보았다. 프로젝트를 진행하며 가끔 사용해 보았던 DispatchQueue를 보다 깊게 이해하고 응용해볼 수 있는 시간이었다🎉

profile
iOS를 개발하는 미니언

0개의 댓글