iOS | Thread, Queue, GCD, QoS

안녕하세용 여러분 ~
구글에서 「iOS, thread, 스레드, queue, swift」 중 혹시 하나를 검색하고 여기까지 오셨나요? 👀

환영해요 ! 번역이 아닌 제가 이해한 내용으로 글을 써보았으니 참고가 되셨으면 좋겠어요
(혹시 잘못된 내용이 있다면 꼭 ! 꼭 ! 꼭 ! 알려주세요 🙇‍♀️)

자주 등장하는 개념들

Task

프로그래밍에서 실행될 일입니다. Swift에서는 Closure로 정의합니다.

DispatchQueue.main.async {
    print("Hello world!")
}

{} 안, closure가 바로 Task가 되는 것이지요.

Task는 closure로 선언되고 DispatchWorkItem 으로 캡슐화 됩니다.

Serial & Cocurrency

Serial
– Task가 직렬로 하나씩 순차적으로 실행되는 개념입니다.

Cocurrency
– 한 번에 1개 이상의 Task가 병렬로 실행되는 개념입니다.

Thread

쓰레드. 한국어로는 실. 🧵
설명하고자하는 Thread는 실 뭉텅이보다 한 가닥의 실이라고 생각하시면 조금 더 이해하시기 쉬울 것 같아요.

Task(프로그래밍에서 실행될 일)들의 흐름을 Thread 라고 합니다.

조금 이해하기 쉬울까 싶어 간단한 그림을 그려봤어요.
Thread(흐름)이 존재하고 Task들이 쌓여서 흘러가고(실행되고) 있어요.

여기서는 간단하게 설명만하고 넘어갈게요.
Thread는 iOS뿐만 아니라 프로그래밍에서 기초가 되는 부분이라 더 좋은 자료를 참고하시길 바랄게요 🙏


Queue

당연히 Queue 에서 쌓인 순서대로 Main thread에서 실행됩니다.

Queue의 종류세 가지가 있습니다.
– Main Queue
– Global Queue
– Custom Queue

1. Main Queue
– Main thread 에서 실행되는 Serial(직렬) Queue 입니다.
– 어느 때 사용하는게 좋은가 : Main Thread인 만큼 당연히 UI Task를 실행시킬 때 사용합니다.

2. Global Queue

– Cocurrent (병렬)로 진행되는 Queue 입니다.
– 어느 때 사용하는게 좋은가 : UI에 관련 없는 Task
– QoS 에서 활용하는 Queue 입니다.
– Global Queue 내에서는 Task의 우선도를 선택할 수 있습니다.
– High, Default, Low, Background
– 4가지를 직접적으로 선택하는 것이 아니라 QoS에 있는 QoSClass 에서 설정합니다. QoS는 따로 살펴보죠

3. Custom Queue

– 개발자가 임의로 만들 수 있는 Queue 입니다.
– 어느 때 사용하는게 좋은가 : Background 에서 순서대로 Task를 실행하고 싶을 때. 실행결과를 또 다시 사용해서 관리를 처리하고 싶을 때.


✔️ 앞에서 작성한 MainQueueGlobalQueue을 그림으로 비교해 보실까요 ~


– 🍱 : MainQueue 에 추가된 Task
– 🧇: GlobalQueu 에 추가된 Task


🍱부터 살펴보죠 !
– Main Queue 에 추가된 Task는 Main Thread에서만 Serial로 실행됩니다.

🧇
– Global Queue 에 추가된 Task는 Concurrency(병렬)로 다양한 Thread로 배치되어 실행됩니다.
– 어느 Thread에서 실행될 것인지는 시스템이 결정합니다.

sync, async

sync
– 앞의 Task 끝날 때까지 기다린다.

async
– 앞의 Task가 끝날 때까지 기다리지 않고, 무조건 우선적으로 실행시킨다.
– 그 유명한 DispatchQueue.main.asyncasync 입니다.


GCD

Grand Central Dispatch

Concurrent(병렬, 동시에 복수의 처리) 실행을 iOS(iPadOS, MacOS)에서 처리할 수 있게
도와주는 API 입니다.

Dispatch Queue를 사용해서 Thread의 Task를 관리합니다.

GCD는 Thread의 관리를 개발자가 조금 더 관여할 수 있게 도와주는 API이기 때문에,
어느 타이밍에 작용합니다 라고 꼭 찝어서 이야기 하기는 힘듭니다.😢

조금은 설명하기 쉬운 위의 그림으로 살펴보죠.

그림의 빨간색 부분에서 표현 하고 있는
「Queue에 Task를 쌓고 Task에 우선순위를 부여하고
원하는 Queue에 지정한 우선순위 그대로 Thread에 전해주는 것」
을 할 수 있게 제공된 API가 GCD 입니다.

물론 GCD가 어느 Thread에서 몇 개의 Thread로 진행되는지는 상황에 따라 시스템이 결정합니다.

QoS

Quality Of Service
https://developer.apple.com/documentation/dispatch/dispatchqos

QoS는 GCD에서 제공되는 class 입니다.

위에서 GlobalQueue에서 Task 우선도를 이야기할 때 잠깐 QoS가 나왔었죠 !
Queue에 추가되는 Task 실행의 우선도를 QoS라고 합니다.

물론 우리, 개발자는 Queue에 Task를 각각 추가시킬 때 QoS(우선도)를 지정할 수 있습니다.

우선도는 6가지로 나뉘어져 있습니다.

QoS Class사용 예소요시간
userInteractiveMain Thread 에서 실행되는 UI 처리.
Animation
UI 업데이트
순식간에 끝 !
userInitiated사용자의 행동에 반응.
저장된 파일을 열거나 할때 사용.
순식간 ~ 몇 초
default개발자가 작업을 분류하는 데 사용되지 않음.
QoS가 지정되지 않은 경우 기본값으로 취급된다.
Global Queue의 우선도.
utility어느정도 계산이 필요하고 즉각적인 결과가 필요하지는 않을 때.
처리 정도가 ProgressBar에 표시할 필요한 처리.
네트워크, 다운로드, 계산, 데이터를 가져오는 처리 등.
몇 초 ~ 몇 분
background뒤에서 실행되어서 사용자에겐 보이지 않는 처리.
백업 등.
사용자가 iPhone에서 저전력모드를 설정해 두면
background 로 설정된Task는 일시중지되어 실행되지 않는다.
몇 분 ~ 몇 시간
unspecifiedQoS 정보가 없음.
https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html#//apple_ref/doc/uid/TP40015243-CH39-SW1

QoS를 잘 활용하면 에너지 효율성이 좋아진다고 합니다 🌝

그래서
어떻게 GCD, QoS를 활용할 것인가.

솔직히 ! Main Queue가 이렇고 Global Queue가 저렇다 해도 실제로 예로 보지않으면 몸에 체감되지 않죠 😥
여러 블로그 글들을 살펴보아도 번역 설명글 중에 마땅한 설명이 없어서
저도 솔직히 개념 이외의 것들은 체감되지 못했어요.

마지막 지푸라기라고 생각해서 WWDC 동영상에서 찾아보니 정말 딱 맞는 발표내용이 있었지 뭐예요.
🔗Concurrent Programming With GCD in Swift 3

저도 개념만 이해한 상태라 솔직히 새로운 예제는 만들지 못할 것 같아서
영상의 발표내용을 제가 이해한 대로 정리해보겠습니다. 💪


발표에서의 전제사항
– UI(User Interface)의 변화 Task, 네트워크 통신 Task 등 다양한 Task들이 존재.
– UI
– Data Transform
– Database
– Networking

발표에서 해결한 문제
– 전제된 4가지 Task를 어떻게 효율적으로 고효율으로 실행할 것인가.


위의 전제를 바탕으로 문제를 해결하기 위해, 3가지 방법을 제안합니다.
1. 비동기
2. Dispatch Group을 활용(비동기)
3. QoS로 우선순위를 정하기


3가지 방법을 자세히 살펴보죠.


1. 비동기

여기에서 설명하고자하는 비동기는 Queue나 우선순위 지정없이,
단순한 Closure를 이용한 비동기 방식입니다.

🔗Concurrent Programming With GCD in Swift 3

Worker는 여러 Task를 갖고 있고 아직 Thread에서 실행시키지 못한 Task들이 존재하고 있네요.

별도의 우선순위 없이 이대로 실행하게 되면 어떻게 될까요?

위의 사진을 살펴보면 Data Task가 Main Thread와 DispatchQueue를 옮겨다니는걸 확인할 수 있습니다.

비디오에서는 Task가 여러 Thread를 옮겨다니게 되면 Thread가 계속 늘어나게(팽창) 됩니다.

그로 인해 Block 된 thread가 더 많은 thread를 또다시 야기할 수 있고,
Cocurrency를 제한할 수 있다고 경고합니다.


정리하자면 비추천 방법이네요…… 😥

2, 3번째 방법에서는 위에서 개념을 설명한 GCD와 QoS를 활용하는 방법이 나옵니다.



2. Dispatch Group을 활용(비동기)

🔗Concurrent Programming With GCD in Swift 3

각자 바통을 이어받아 일을 해결하는 것과 그룹지어 일을 나눠서 함께 해결하는 것.
어느 것이 빠른시간 내에 일을 효과적으로 끝낼 수 있을까요?

당연히 그룹지어 함께 해결하는게 더 빠르고 효율적이겠죠.

Thread에서도 같은 원리도 동작합니다.

4가지 task를 하나의 그룹으로 생각합니다.

그리고, UI 이외의 3가지 task가 끝났을 때 notify를 사용해서 UI를 갱신시킵니다.
코드는 아래와 같습니다.

let label = UILabel()

func useDispatchQueue() {
    let dispatchGroup = DispatchGroup()

    let queue1 = DispatchQueue(label: "Data Transform")
    let queue2 = DispatchQueue(label: "Database")
    let queue3 = DispatchQueue(label: "Networking")

    dispatchGroup.notify(queue: DispatchQueue.main) {
        label.text = "UPDATED!"
        print("🧚‍♀️ update label text")
    }

    queue1.async(group: dispatchGroup) {
        print("🔗 \(queue1.label)")
    }

    queue2.async(group: dispatchGroup) {
        print("🔗 \(queue2.label)")
    }

    queue3.async(group: dispatchGroup) {
        print("🔗 \(queue3.label)")
    }
}

useDispatchQueue()

// 🌟print 결과 🌟
//// 🔗 Networking
//// 🔗 Data Transform
//// 🔗 Database
//// 🧚‍♀️ update label text

Dispatch Group 을 사용했을 때의 장점
– 가장 마지막에 작동시킬 task를 지정할 수 있습니다.
(Ex. 여러개의 네트워크 통신이 최종적으로 끝난 타이밍에 UI 업데이트를 지정 )
1. 비동기에서 문제가 되었던, thread 의 팽창을 group 내에서 제거 가능합니다.


group 을 사용했을 때의 간과할 수 있는 포인트
– 비동기적 실행이기 때문에 마지막으로 실행될 task 이외의 task의 우선순위가 구분되지 않습니다.



3. QoS로 우선순위를 정하기


task들의 순위를 어느정도 개발자가 제어할 수 있는 방법이 필요할 때,
1.과 2. 모두 비동기적 실행으로 썩 내키지 않을 때,
QoS가 실력을 발휘할 때 입니다.

위에서 설명드린대로 QoS에서는 task의 우선순위를 4가지로 정의하고 있습니다.

func useDispatchQueue() {
    let queue = DispatchQueue(label: "unnnyong's queue", attributes: .concurrent)

    queue.async(qos: .utility) {
        for i in 0...5 {
            print("2️⃣ Data Transform : \(i)")
        }
    }

    queue.async(qos: .background) { // 가장 우선도 낮음
        for i in 0...5 {
            print("3️⃣ Database : \(i)")
        }
    }

    queue.async(qos: .userInteractive) { // 가장 우선도 높음
        for i in 0...5 {
            print("1️⃣ Networking : \(i)")
        }
    }
}

useDispatchQueue()

//// 🌟결과 🌟
// 2️⃣ Data Transform : 0
// 1️⃣ Networking : 0
// 3️⃣ Database : 0
// 1️⃣ Networking : 1
// 3️⃣ Database : 1
// 1️⃣ Networking : 2
// 2️⃣ Data Transform : 1
// 1️⃣ Networking : 3
// 2️⃣ Data Transform : 2
// 1️⃣ Networking : 4
// 3️⃣ Database : 2
// 1️⃣ Networking : 5
// 2️⃣ Data Transform : 3
// 2️⃣ Data Transform : 4
// 2️⃣ Data Transform : 5
// 3️⃣ Database : 3
// 3️⃣ Database : 4
// 3️⃣ Database : 5

「utility, background,userInteractive」 3가지를 사용해서 우선도를 task별로 지정해주고
이모지를 사용해서 알아보기 쉽게 표현해보았어요.


역시나 역시나!
가장 우선순위를 높게 잡았던 「1️⃣ Networking」 가 반복문이 가장 먼저 끝나고
가장 우선순위를 낮게 잡았던 「3️⃣ Database」 가 반복문이 가장 마지막에 끝났네요.



🐥 번외

그러면 마지막으로 실행될 task는 정해져 있고, 이 전에 처리될 task들은 우선순위가 필요할 때,
어떻게 해야할까요?

2에서 설명한 Dispatch group과 3에서 사용한 QoS를 활용해보죠.

let label = UILabel()

func useDispatchQueue() {
    let dispatchGroup = DispatchGroup()
    let queue = DispatchQueue(label: "unnnyong's queue", attributes: .concurrent)

    dispatchGroup.notify(queue: DispatchQueue.main) {
        label.text = "UPDATED!"
        print("🧚‍♀️ update label text")
    }

    queue.async(group: dispatchGroup, qos: .utility) {
        for i in 0...5 {
            print("2️⃣ Data Transform : \(i)")
        }
    }

    queue.async(group: dispatchGroup, qos: .background) { // 가장 우선도 낮음
        for i in 0...5 {
            print("3️⃣ Database : \(i)")
        }
    }

    queue.async(group: dispatchGroup, qos: .userInteractive) { // 가장 우선도 높음
        for i in 0...5 {
            print("1️⃣ Networking : \(i)")
        }
    }
}

useDispatchQueue()

코드와 같이 queue에 task를 추가시킬 때, group과 qos를 함께 지정해주면 됩니다 🧚‍♀️

마무리

“Thread, Queue. 그래. 의미는 알겠어.
어떻게 나눠서 생각해야하고 실제로 코드로 잘 활용해야하는거야?”

위의 마인드가 제가 이 글을 정리하고 있었을 때의 마인드였지만,
여차저차 한국, 일본의 다양한 블로거분들과 다양한 자료로 머리속을 정리하며
이 글을 마무리하니 이제서야 감을 찾은 것 같네요.

복잡한 UI와 네트워크 통신이 계속되는 실제 프로젝트를 맡지않는 이상에는
QoS나 GCD를 200% 활용할 자신은 없어도
이 글로 맞닥뜨릴 자신은 생겼네요.

혹시라도 이 글에서 잘못된 부분이 있으면 코멘트나 메일로 연락 부탁드려요 !
피드백은 언제나 환영합니다 😖💜

부족하지만 긴 글 읽어주셔서 감사해요 ! 오늘도 즐거운 iOS 개발되세용 ☕️


참고자료

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중