맛동산이

GCD, Dispatchqueue란? 본문

앱/Swift

GCD, Dispatchqueue란?

진ddang 2023. 3. 22. 23:18

역시나 킹갓 제드님이 다 정리해두셨다.

iOS ) Concurrency Programming Guide - Concurrency and Application Design

스위프트 프로그래밍을 처음 접하다 보면, api 에서 어떠한 값들이 변화하고 그 값을 통해서 ui를 변경 해주는 경우

dispatch queue.main.async 라는 명령어를 통해서 ui를 변경해준 경험이 존재 할것이다.

없음말고, 무튼 나는 있다.

근데 이렇게 dispatchqueue.main.async가 뭔지 알기 위해서 이렇게 글을 쓴다.

쓰레기같은 코드.

GCD( grand central dispatch )의 발단

우리컴퓨터에서 연산처리속도는 cpu의 성능에 따라 정해진다. 하지만 이러한 연산 속도를 올리기 위해서 cpu를 쉬지 않고 돌린다면, 열로 인하여 cpu의 성능이 떨어지는것을 발견하고, 코어수를 늘리는 방식을 채택하였다.

하지만 코어수를 늘리더라도 그 하나하나의 cpu가 작업을 하나를 처리하면서, 응답을 기다리는 동안 cpu의 쉬는 타임이 발생하고 이러한 것은 cpu의 성능을 100프로 사용한다고 할수가 없다. 따라서 우리는 cpu 내부의 쓰레드 즉 프로세스의 내부에서 실제로 작업을 수행하는 객체 를 늘리는 방식으로 해결하였다. 즉 한놈이 대기를 위해 잠깐 쉴동안 한놈을 굴리는 것이다.

마치 하나의 방 안에서 두명이서 일을 하다가 한명이 화장실을 갔다 올 사이에 한놈이 계속해서 일하는 노예가 하나 늘었달까?

기존 언어에서의 쓰레드 처리

기존 자바나 c++ 과 같은 언어에서는 명시적으로 쓰레드를 생성하고, 작업을 또한 명시적으로 쓰레드에 부여해야하는 방식이었다. 하지만 이는 개발자에게 많은 부담과 책임이 가는 환경이며, 또한 만드는 과정 자체도 어려움이 많다.

애플이 해줄게!

이러한 쓰레드를 애플은 GCD라는 기술을 통해서 앱에서 만든 해당 코드를 시스템 수준의 코드로 변경해주어 쉽게 스레드 관리를 할수 있게 하였다.

GCD란 다중스레드를 만들어 내고 이를 프로그래머가 처리하는것이 아닌, mac os가 관리해주는 기능을 의미하며, 이를 통해서 프로그래머는 편리하게 비동기 처리를 스레드에 할당하여 사용할수 있게 된다.

또한 해당 dispatchqueue.main과 dispatchQueue.global()을 어플리케이션 실행 시작 부분에서 인스턴스를 생성하고 .main에서 UI관련한 모든 것을 처리해주는것을 기본으로 만들어 두었다.

따라서 sync하게 ui를 처리해버리면, 만약 다른 queue에서 현재 main에 있는 ui인스턴스를 건들일수 없게 되고, 계속해서 기다리게 되는 deadlock이 걸리게 될 위험이 있기 때문에 애플에서는 이를 방지 하고자, main큐가 아닌경우에는 ui를 변경할수 없도록 한것이다. (밑의 내용을 읽으면 이해가 감)

Dispatch queue

애플공식문서에는 다음과 같이 나와있다.

디스패치큐는 클래스다. 디스패치 큐는 추상적인 클래스기 때문에 실질적으로 메모리에서 동작하는것은 디스패치큐의 인스턴스가 되는것이고 그 종류가, main과 global이 되는것이다. (클래스 안에 있는 타입 프로퍼티와, 타입 매소드)

dispatchqueue의 종류는 크게 두개가 있다.

  1. serial : 디스패치 큐에 들어온 작업을 하나씩 처리하게 되는것.
  2. concurrent : 디스패치 큐에 들어온 작업을 하나씩 처리하게 되지만, async로 들어오게 된다면, 병렬적으로도 처리할수 있는 큐
  • 이거는 이제 dispatchqueue를 선언할때 넣어주게 된다.
  • .attribute로 넣어주게 됨

그렇다면 뒤에 .async와, .sync는 무었일까??

둘다보면 instance method이다.

실제 사용 코드를 보게 되면,

이렇게 보게 되면, sync는 함수를 프로퍼티로 받는 클로저 이며, async또한 dispatchWorkItem이라는 함수를 캡슐화 한 프로퍼티를 받게 된다.

즉 둘다 async한. task를 함수로 받게 되는것이다.

이를 이미지화 하면 다음과 같이 된다.

이렇게 task를 클로저 형태로 큐에 넣게 되고, dispatchqueue는 당연히 큐 이기 때문에 serial이던, concurrent이던 하나씩 작업을 뽑아내기 시작한다.

이렇게 하나씩 뽑아내는 작업을 serial하게 처리한다는 것은 앞의 task1개가 끝나야 뒤의 task2를 시작한다는 것이고,

concurrent하더라도 클로저로 받은 task가 sync인지 async인지에 따라서 sync라면 task1이 완전히 끝나야 task2 로 이동한다는 것이며, task2가 async이고, task3가 다시 async이기 때문에 이를 병렬적으로 처리해주기 시작한다는 것이다.

라고 생각햇는데 실습을 해보니 다른 결과가 나왓다.

실습

import Foundation

func printNum(range: Array<Int>, delim: String) {
    print("----------\\(delim)----------")
    for i in range {
        print(i)
    }
}

let jinQueue = DispatchQueue(label: "jinyong", attributes: .concurrent)
let queueInQueue = DispatchQueue(label: "queueInQueue", attributes: .concurrent)

jinQueue.async {
    printNum(range: [1,2,3], delim: "a")
}

jinQueue.async {
    printNum(range: [1,2,3], delim: "a")
}

jinQueue.async {
    queueInQueue.sync{
        printNum(range: [1,2,3], delim: "s-p1")
    }
    queueInQueue.sync {
        printNum(range: [4,5,6], delim: "s-p2")
    }
}

jinQueue.sync {
    printNum(range: [1,2,3], delim: "s")
}

jinQueue.sync {
    printNum(range: [4,5,6], delim: "s")
}

결과값

결과값을 돌려보니까, 병렬적으로 처리해줄때, (concurrent)일때는, sync와 async사이에서 둘다 결국 병렬적으로 처리해주게 된다. 즉 다시 위에 설명으로 돌아가자면,

task1이 끝난시점에서 task2, task3, task3가 병렬적으로 처리 되게 되는것이다. 그리고 task2, task3, 가 끝나는 시점은 아무도 모르며, task4가 만일 sync로 있다면, task3가 끝난시점에서 무조건 돌아가게 되는것이다.

디스패치 큐의 장점

  • Dispatch queues는 이해하기 쉽고 간단한 인터페이스를 제공합니다.
  • Dispatch queues는 자동 및 전체적인 쓰레드 pool관리 기능을 제공합니다.
  • Dispatch queues는 튜닝된 어셈블리의 속도를 제공합니다.(이걸..어떻게 번역해야할지..They provide the speed of tuned assembly. )
  • Dispatch queues는 쓰레드 스택이 앱 메모리에 남아있지 않기 때문에 훨씬 효율적입니다.
  • Dispatch queues는 로드가 걸리면 커널에 트랩되지 않습니다. (They do not trap to the kernel under load.)
  • Dispatch queues
  • 에 작업(task)을 비동기적으로 전달해도 대기열을 교착상태로 만들 수 없습니다.
  • Dispatch queues는 투쟁(contention)하에 우아하게(진짜 gracefully라고 나와있음..) 확장됩니다.
  • Serial dispatch queues는 lock 및 기타 동기화 primitives보다 더 효율적인 대안을 제공합니다.

무튼 대충 이렇게 dispatch queue를 통해서 스레드나눌수 있고, 다양한 장점이 있다는 것이다.

dispatch queue의 사용법

Qos 옵션

The quality-of-service level to associate with the queue. This value determines the priority at which the system schedules tasks for execution.

qos는 quality of service의 약자로, 실행의 우선순위를 의미한다. 음… 살짝 운영체제에서 배운 멀티레벨큐가 생각나지요잉?????????

무튼 그렇다. 여러개의 큐를 나눈다음. 그 안에서 또 우선순위를 두는 옵션인것이다.

 

qos의 종류(우선순위)

  1. userInteractive: 가장 높음! 사용자와 직접 상호작용을 하는 작업을 의미하기 때문에 사용자의 요청에 곧바로 응답이 있어야한다. 애니메이션 처리, 이벤트 처리, UI 업데이트 등의 경우입니다. 그래서 가장 높은 우선순위를 가진다.
  2. userInitiated: 사용자가 어떤 요청을 했을 때 그 결과를 곧바로 받아야하거나 사용자가 앱을 사용하는 것을 순간적으로 막기 위한 경우. 예를 들면 사용자에게 보여줄 이메일 내용을 로드할 때, 이 qos를 사용할 수 있다.
  3. default: 기본값으로 일반적인 작업의 경우. ( 그냥 파라미터 없을때 .default값이 설정됨.)
  4. utility: 사용자가 앱을 계속 사용하지 않도록 막지 않는 작업에 사용할 수 있다. 예를 들면 이 qos는 사용자와 상호작용하지 않으면서 오랜시간동안 어떤 작업을 진행해야할 때 사용할 수 있다.
  5. background: 가장 낮은 우선순위를 가지는 qos이고, 앱이 백그라운드에서 실행중일 때 진행하는 작업에 이 qos를 사용할 수 있다.

정리

dispatchqueue는 결국 스레드를 조금 쉽게 처리하게 하라고 만든 uikit에 구현되어있는 클래스이다.

dispatchqueue는 concurrent와 serial queue로 종류가 두개가 있으며, serial은 task하나가 끝나야 다음 테스크를 하게 되는 큐, 이며 concurrent는 병렬적으로 일을 진행 하게 된다. (병렬적이라 해도 빠르게 스위치 되는것일 뿐! -운영체제)

dispatchqueue에서는 .async와 .sync클로저 를 이용해서 task를 보내주게 되는데 이때 QueueWorkIteam이라는 캡슐화를 사용할수도 있다.

만약 concurrent 한 queue라면, queue내부에서 async로 받은 작업들은 병렬적이게 다 처리가 될것이고, snyc와 서로 무관한 순서를 가지게 된다. ( 둘다 병렬적으로 처리되는것)

하지만 snyc작업은 snyc작업들 끼리의 순서를 가지게 되는것이다.

반응형