Swift

[Swift] Swift의 동시성 프로그래밍 (1) | GCD

하이D:) 2024. 3. 17. 19:37

 

GCD는 iOS에서 비동기 처리를 위해 반드시 알아야 하는 개념이다.

 

프로젝트를 만들면서 메인스레드만을 가지고 작업하는 경우는 거의 없죠??

왜냐.. 네트워크 통신 등 시간이 드는 작업들은 비동기 처리를 해주어야 앱이 자연스럽게 돌아가는 것처럼 보이기 때문! 현대인들은 조금의 딜레이도 못 참지..!

 

이렇게 비동기 처리를 해줄 때 보통 메인 스레드에서 가지고 있었던 task를 다른 스레드와 함께 나누면서 여러 개의 스레드를 사용하게 되는데  이러한 멀티스레드에서의 환경에서 반드시 알아야 하는 개념이 바로!  동시성 프로그래밍이다. 

 

 iOS가 동시성 프로그래밍을 지원하는 대표적인 기술이 GCD 인 것이다. 

 

동시성 .. Concurrency.. 많이 들어보긴 했는데 발음도 비슷한 비동기, 동기와는 또 어떻게 다른데! 라는 의문이 생길 수밖에 없다. 나도 매번 헷갈리기 때문에 이 김에 정리해 보는 걸루

 

 

 

[Swift] Swift의 동시성 프로그래밍 (1) | GCD

[Swift] Swift의 동시성 프로그래밍 (2) | Swift Concurrency의 등장 (feat. async-await & Coroutine & Continuation)

[Swift] Swift의 동시성 프로그래밍 (3) | async/await (WWDC 2021)

 

 

# GCD란?

 

  • 멀티 스레드 환경에서 편리하게 개발할 수 있도록 하는 기술.
  • 동시성 프로그래밍 (여러 Thread에서 작업하는 것)을 위해 필요한  개념.
  • 우리가 main Thread에서 Queue(대기열) 에 작업을 보내면 그에 따른 스레드를 적절히 생성/실행/제거 해주는 역할을 함.

 

 

즉, 우리 같은 개발자들은 메인스레드에서 실행하던 작업을 Queue(대기열) 즉, GCD에서 사용하는 큐인 Dispatch Queue에 넣어주기만 하면 스래드 생성, 조작은 OS에서 관리해 주는 것이다.

 

 

※ 그럼 코드를 작성할 때 Queue큐에 보내는 작업을 해주지 않았다면? 

=> 메인스레드에서 모든 작업들이 할당되어서 실행되어 왔던 것. ( 메인스레드 힘들었겠네.. )

 

 

※ 이렇게 메인스레드에만 할당되어 있던 일들을 GCD라는 기술을 통해 다른 스래드에 분배하면!

 => 메인스레드가 일을 함과 동시에 다른스레드들도 일을 할 수 있는 것

=> 이게 바로 "동시성 프로그래밍"인 것이다

 

 

※  어떻게 GCD에서 제공하는 API 사용할 수 있는가?

DispatchQueue.global().async {

}

 

위의 예시 코드가 뜻하는 것은

: Dispatch Queue의 종류 중에 하나인 Global DispatchQueue에 작업을 보내고 비동기(async)로 동작하도록 한다.

 

 

 

 

 

 

# 비동기Async/동기Sync, 직렬Serial/동시Concurrent

 

자 그럼 이쯤에서 비동기Async/동기Sync, 직렬Serial/동시Concurrent 각각의 개념이 무엇인지 짚고 넘어가 보자

 

비동기Async/동기Sync

  • 비동기Async
    • 메인스레드가 작업을 다른 스레드에서 하도록 시킨 후 “안기다리고” 다음 작업
  • 동기Sync
    • 메인스레드가 작업을 다른 스레드에서 하도록 시킨 후 “기다리고” 다음 작업

 

직렬Serial/동시Concurrent

  • 직렬Serial
    • 메인스레드가 다른 큐에 분산시킨 작업을 다른 하나의 스레드에 모두 넘긴다.
    • "순서가 중요한 작업"을 하나의 스레드에서 순서대로 처리
    • main queue는 직렬로 동작한다.(어차피 하나의 스레드여서)
    • 효율적인 작업을 위해 concurrent가 좋아 보이는데 직렬이 필요한 이유가 있을까?
      • 특정 작업이 끝난 후 그 결과물을 활용해서 다른 작업을 실행하는 등 순서가 중요한 경우 ex. 이미지 다운받고 그 이미지 활용하는 경우
  • 동시Concurrent
    • 메인스레드가 다른 큐에 분산시킨 작업을 여러 개의 스레드로 작업 분산해서 넘김
    • 각자 독립적이지만 유사한 여러개의 작업을 여러 스레드에서 처리

 

 

 

 

# 디스패치 큐의 종류

GCD란 개발자가 메인스레드에서 실행하던 작업을 Dispatch Queue(대기열) 에 넣어주기만 하면 스래드 생성, 조작은 OS에서 관리해 주는 기술이라고 했는데 이 Dispatch Queue(대기열)에도 종류가 있다.

 

 

  • 메인큐
    • 생성코드 DispatchQueue.main
    • 메인스레드, 1번 스레드
    • Serial(직렬)로 동작한다(하나의 스레드밖에 없으니 당연)
    • GCD 사용 시 주의할 점 : UI 작업을 반드시 메인큐에서!! 다른 스레드에서 비동기 작업을 하다가도 UI 작업(화면을 다시 그리는 작업)은 반드시 메인큐에서 해야 한다.

 

 

  • 글로벌큐
    • 생성코드 DispatchQueue.global(qos: )
    • Concurrent(동시)로 동작한다. (왜냐?! QOS 별로 여러 스레드 생성가능하기 떄문에!!)
    • 생성코드에서 볼 수 있는 것처럼 큐 생성 시, qos라는 것을 지정해 주어서 시스템이 큐의 중요도/우선순위에 따라 더 많은 스레드를 배치하고 배터리를 집중해서 사용하도록 한다.

 

 

  • 프라이빗(커스텀)큐
    • 생성코드 DispatchQueue.global(label: "…" )
    • 디폴트로는 Serial(직렬)로 동작하지만 Concurrent(동시)로 동작하도록 설정해 줄 수 있다.
    • Concurrent(동시)로 동작하도록 변경해 주면 글로벌큐처럼 qos 설정이 가능해진다.

 

 

 

#QOS(Quality of Service)

위에서 알아본 디스패치큐의 종류 중, 글로벌 큐와 프라이빗 큐처럼 concurrent하게 동작하도록 하는 큐에서 qos라는 것을 지정해 줄 수 있다고 했는데 여기서 갑분 등장한 QOS는 무엇일까?

 

QOS란?

말 그대로 큐의 퀄리티(큐의 중요도, 우선순위라고도 볼 수 있다.)
글로벌/커스컴 큐처럼 concurrent 하게 동작하도록 하는 큐(대기열)를 사용할 때 qos를 사용하여 각 큐의 중요도(작업의 중요도)를 결정할 수 있다.

 

 

QOS를 왜 사용하는가?

대기열(큐)의 우선순위를 설정하여 한정된 자원과 에너지를 효율적으로 사용할 수 있다.

 

중요한 대기열에 스레드 더 배치해서 효율적으로 작업이 이루어질 수 있도록 하는 거죠?!

 

 

QOS 설정에 따른 내부 동작 방법

  • 큐의 우선순위(QOS)가 높으면 스레드를 더 많이 사용하고 CPU 코어 적극적으로 써서 작업을 빠르게 실행하고
  • 큐의 우선순위(QOS) 낮으면 스레드를 적게 사용하고 CPU 코어 절약을 시도한다

요런 느낌쓰!

 

 

 

QOS의 6가지 종류

  • userInteractive
    • 사용자와 직접적인 상호작용을 하는 작업일 때!
    • 예를 들어 애니메이션이나 앱의 인터페이스를 업데이트하는 등 UI 관련 이벤트를 처리할 때 사용
  • userInitiated
    • 사용자가 initiated 한 뒤로 즉각적인 처리가 이루어져야 하는 작업일 때!
    • 예를 들어 파일을 열거나, 유저가 유저인터페이스에서 무엇인가를 클릭한 뒤의 액션을 처리하거나 api로부터 데이터를 로딩하는 작업 등을 수행할 사용
  • default
    • qos를 선택하지 않으면 기본값으로 선택되는 qos
    • userInteractive와userInitiated 보다는 중요도가 낮고, utility background보다는 높은 중요도를 갖는다.
  • utility
    • 즉각적인 결과가 필요하지 않은 경우에 사용
    • 예를 들어 데이터 다운로드 및 불러오거나 progress indicator와 함께 길게 실행되는 작업. 계산, I/O, networking 등에서 사용
  • background
    • 유저에게 직접적으로 보이지 않고 백그라운드에서 처리되는 작업의 경우
    • 데이터 미리 가져오기, 로컬 DB에 데이터를 저장하는 작업, 백업, 동기화 등의 우선순위가 높지 않은 작업에 사용
  • unspecified

 

 

 

 

 

 

다시 한번 디스패치 큐의 종류 QOS설정을 연관 지어 아래 질문을 살펴보면

 

왜 메인큐(❌) 글로벌큐(⭕️)에서 QOS 설정이 가능한가?

  • 메인큐는 직렬(Serial)로 동작 & 어차피 하나의 스레드(1개의 메인 스레드)밖에 없기 때문에 큐의 우선순위 설정 불가
  • 글로벌큐는 QOS 별로 여러 스레드 생성가능 → 여러 큐에서 여러 스레드에 일이 배치되어 동시(concurrent)에 여러 개의 일을 비동기로 처리하기 때문에 가능
  • 커스텀큐도 원래는 직렬(Serial)이지만 파라미터를 통해 동시(concurrent)로 변경가능 → 동시(concurrent)로 변경하면 qos 설정 가능

 

 

 

 

 

그리고 이러한 동시성 프로그래밍의 관점에서 Swift 5.5 이후에 도입된 방법은 조금 다른데

 

Swift 5.5에서 새로운 동시성 모델을 제시하기 까지는 GCD CompletionHandler 사용해 비동기 프로그래밍을 작성해왔습니다.  GCD API 비동기 작업을 처리하고, CompletionHandler 비동기가 끝나는 시점에 필요한 작업을 수행한거죠.

근데 Swift 5.5 이후에는 async/await와 같은 새롭게 도입된 비동기 프로그래밍 개념이 등장하는데 이후 글에서 자세히 살펴볼 예정입니다~~!