본문 바로가기

iOS

(동기/비동기)와 동시성에 대한 개념

앱 개발자의 기초이자 중요한 개념인 동기와 비동기, 그리고 동시성에 대해 정리합니다.

 

동기 vs 비동기

동기와 비동기의 개념에 대해 살펴보기 전에 우선 쓰레드에 대한 이해가 필요합니다.

 

쓰레드의 정의를 보면 ''프로그램(프로세스)의 실행단위"라 명시되어 있습니다.

 

쉽게 말해 쓰레드란 Task를 처리하는 단위이자 흐름이며, 

 

프로세스 내에서 실제로 작업을 수행하는 주체입니다.

 

본인이 앱 개발자인 만큼 앱을 기준으로 설명하겠습니다.

앞에서 설명한바와 같이 쓰레드는 일의 흐름이며 task를 할당받아 일을 처리합니다.

 

앱은 여러개의 쓰레드를 갖고,

 

일반적으로 별도의 처리없이 코드를 작성한 경우 task들은 하나의 쓰레드(메인 쓰레드)에 적재되고 처리됩니다.

 

->  task1의 비용이 크다면, task2는 task1이 완료될때까지 대기해야한다는 뜻이 됩니다.  

 

 

예를 들어, 용량이 큰 이미지를 다운받고 해당이미지를 화면에 표시하는 작업이 있다고 가정하겠습니다.

 

이미지를 네트워크 통신을 통해 다운 받는 작업을 task1,

 

다운받은 이미지를 화면에 보여주는 작업을 task2라고 하겠습니다.

프로그램은 이미지가 다운완료되는 시간까지 다른 작업을 할 수 없고,

 

해당 시간동안 유저의 인터랙션 또한 제한됩니다.

 

 

그렇다면 동기와 비동기는 무엇일까요?

 

사전적인 의미를 살펴보면,

 

동기란 일상 생활에서 군대 동기, 학교 동기와 같이 표현하는 동기의 개념과 같이

 

"시작과 끝이 일치한다" 를 뜻합니다.

 

반대로 비동기란 당연히

 

"시작과 끝이 일치하지 않는다" 가  되겠네요. 

 

무엇이 일치하고 무엇이 일치하지 않는다는 것일까요?

 

위에 예제를 통해 설명 드리겠습니다.

 

 

우선, 앞서 설명드린 상황에서 우리는 하나의 쓰레드만 이용하여 task를 할당하였습니다.

 

하지만, task가 끝나기 전에는 다른 task를 할당할 수 없다는 이슈가 있으니,

 

저희는 Thread2에 task를 나누어서 할당할 것 입니다.

사진과 같이 이미지를 다운받은 task를 thread2에 할당하였습니다. 

여기서 시작과 끝은 각 task1이 시작된 시점과 task1이 마무리 된 시점을 뜻합니다.

 

동기 -> 시작과 끝이 같다.

비동기 -> 시작과 끝이 다르다.

 

말 그대로 동기란, thread1은 task1을 다른 쓰레드에 할당한 뒤 해당 task가 완료될때까지 대기함을 뜻합니다.

 

반대로 비동기란, thread2로 할당된 task1의 작업 완료 유무랑 관계없이 바로 리턴되고,

 

thread1은 즉시 다른 task를 할당 받을 수 있습니다.

 

이미지를 다운받는 작업을 thread2에 할당하고, 

 

해당작업이 언제 끝날지는 모르지만, thread1은 계속해서 다른 task를 실행하고,

 

task1이 완료되는대로 다운받은 이미지를 화면에 보여주는 task2를 실행한다면,

 

이미지를 다운 받는 동안 대기해야하는 이슈는 해결이 될 것입니다.

 

그렇다면 동기보다는 비동기가 무조건 좋은거 아닌가?

 

라는 의문을 가지실 수 있습니다. 

 

하지만, 동기 처리와 비동기 처리는 각각 장단점을 갖고 있습니다.

 

비동기 처리의 경우, 자원을 효율적으로 사용할 수 있지만 그만큼 설계가 복잡해진다는 단점이 있고,

 

반대로 동기 처리의 경우 직관적이고 설계가 간단하지만, 결과가 주어질때까지 대기해야하는 단점이 있습니다.

 

동기와 비동기 처리의 개념에 대해 이해하고 상황에 맞게 처리해주는게 현명한 방법이라고 생각합니다.

 

 

동시성 프로그래밍

동시성이란 무엇일까요?

 

동시성이란 말 그대로 여러가지 task들을 동시에 처리하는 것입니다.

 

비슷한 개념으로는 병렬성 프로그래밍이 존재하는데,

 

병렬성 프로그래밍은 task들을 물리적으로 동시에 처리하는 것이며,

 

동시성 프로그래밍의 경우, 라운드 로빈, 시분할 처리와 같이 동시에 처리되는 것처럼 처리하는 방식입니다.

 

해당 내용의 경우 나중에 따로 글을 작성하도록 하겠습니다. 

 

동시성 프로그래밍을 설명하기에 앞서 Queue의 개념을 이해해야 합니다.

 

간단하게 설명하면, Queue란 FIFO(First In First Out)의 구조를 갖는 형태로,

 

먼저 들어간 task가 먼저 처리(삭제) 되는 형태입니다.

 

iOS의 경우 각 main thread에서 처리될 task들을 특정 Queue에 할당해줍니다.

 

OS는 개발자가 할당한 task들을 담겨있는 Queue의 특성에 따라 쓰레드에 분배해주며, 

 

이 Queue는 크게 2가지로 나누어집니다.

 

1. SerialQueue (직렬 처리)

2. ConcurrentQueue (동시성 처리)

 

3. CustomQueue (개발자 생성 큐)

 

 

우선 우리의 main thead(thread1)는 4개의 task를 할당 받았고

해당 task들을 처리하는데 있어 다른 쓰레드에 일을 할당하려 합니다.

 

먼저, 직렬큐(SerialQueue)에 담아 주겠습니다.

 

직렬큐에 task들을 할당시 OS는 다음과 같이 다른 하나의 쓰레드(thread2)에 모든 task를 할당하며,

 

해당 task들은 당연히 실행의 순서를 보장 받습니다.  

 

 

그렇다면 ConcurrentQueue에 할당 시 OS는 쓰레드에 어떻게 할당할까요?

예상하셨던 것과 같이 여러 개의 쓰레드에 분산시켜 처리하며, 

 

몇개의 쓰레드에 할당할지는 OS가 결정하게 됩니다.

 

정리하자면, 하나의 쓰레드(메인 쓰레드)에 주어진 여러개의 task(일)들을,

 

다른 쓰레드에 할당하여 자원을 소비하고자 할때,

 

개발자는 해당 task들을 특정 Queue에 담아 줍니다.

 

직렬처리를 위해 SerialQueue에 담아주었을 경우,

 

task들은 다른 하나의 쓰레드에서 순서가 보장된 채로 처리됩니다.

 

동시처리를 위해 ConcurrentQueue에 담아주었을 경우,

 

task들은 여러개의 쓰레드에서 분산 처리되며, 

 

쓰레드의 개수는 OS가 결정합니다.

 

여기서도 의문이 듭니다.

 

그렇다면 효율성을 위해 직렬처리보다는 무조건 동시 처리하는 것이 옳지않나요?

 

하지만, 프로그래밍을 하다보면 작업 순서의 보장이 매우 중요할 경우가 있습니다.

 

이 경우, 개발자는 직렬처리를 항상 사용해야합니다.

 

따라서 위 질문에 대해,

 

직렬(Serial) 처리 - 순서가 중요한 작업을 처리할 때 사용

동시(Concurrent) 처리 - 각자 독립적이지만 유사한 여러개의 작업을 처리할 때 사용

 

정도로 정리할 수 있을것 같습니다.

 

 

GCD

그렇다면, 위 개념을 통해 코드를 어떻게 작성하면 될까요?

 

사실 iOS 프로그래밍을 하면서 실제로 개발자가 쓰레드를 정의하고

 

task를 할당해주는 작업을 일반적인 환경에서 쉽게 볼수 없습니다.

 

iOS 기준, 개발자가 task들을 대기행렬에 넣어주기만 하면,

 

적절한 쓰레드로 분산시켜주는 작업을 OS에서 해주기 때문입니다.

 

그리고 개발자가 직접 쓰레드를 관리하지 않도록 해주는 이 도구를

 

GCD(Grand Central Dispatch) 라고 칭합니다.

 

즉, 큐에 작업을 넣는 것 개발자가,

 

큐에 들어온 작업을 쓰레드에 분배하는 것은 OS가 담당합니다.

 

개발자는 쓰레드보다 상위 레벨에서 작업하는 것이라고 보면 됩니다.

 

정리하자면 위 이미지와 같은 형태가 됩니다.

 

DispatchQueue

GCD에게 task 할당 해주기 위해 개발자는 DispatchQueue를 사용하여

 

DispatchQueue.{Queue}({QoS}).{sync / async} {

    //code...

}

 

다음과 같은 형태로 task 할당을 요청하게 됩니다.

 

우선, DispatchQueue가 가질 수 있는 큐에 대해 설명드리겠습니다.

 

DispatchQueue는 크게 main 큐와 global 큐로 나누어집니다.

 

 

main Queue

 

메인 스레드에서 작업을 보관하고 수행하는 큐

 

메인 스레드에서 동작하기 때문에 단 하나만 존재할 수 있고, Serial 특성을 갖습니다.

 

즉, 큐에 쌓이는 작업을 순차적으로 실행합니다.

 

유의할 점은,

 

화면 위에 보여지는 앱 요소들은 모두 메인 스레드에서 그리고 있습니다.

 

따라서 *UI 업데이트와 관련된 모든 작업은 다른 큐가 아닌 Main Queue에 할당되어야 합니다.

 

=> Cocoa Touch 앱에서 UIApplication의 인스턴스가 main thread에 붙기(attach) 때문.
UIApplication은 앱을 시작할 때 인스턴스화 되는 앱의 첫번째 부분인데,
얘는 앱의 run loop를 포함하여 main event loop를 설정하고 event처리를 시작합니다.
앱의 main event loop는 touch, gesture등의 모든 UI event를 수신합니다. 

 

즉, 메인 큐란 "메인 스레드에서 작업을 보관하고 수행하는 큐"이고,

 

앱의 Run Loop와 함께 작동하여 큐에 있는 task의 실행을

 

Run Loop에 연결된 다른 이벤트 소스의 실행과 얽힙니다.

 

따라서, UI와 관련된 모든 event가 main thread에 붙기때문에, UI 관련 업데이트는 main queue에서 이루어져야 합니다.

 

 

global Queue

 

동시에 하나 이상의 task를 실행하지만 task는 큐에 추가된 순서대로 시작됩니다.

 

현재 실행중인 task는 dispatch queue에서 관리하는 고유한 쓰레드에서 실행됩니다.

 

 

 

QoS(Quality of Service)

 

그렇다면 QoS는 무엇일까요?

 

QoS(Quality of Service)란 중요도라고 생각하시면 됩니다.

 

작동시킬 qos를 지정함으로써 중요도를 표시하고,

 

시스템이 우선순위를 정하고 이에따라 스케쥴링을 정합니다.

 

우선순위가 높은 작업은 우선순위가 낮은 작업보다 더 빨리 수행되고,

 

리소스가 많으므로 일반적으로 우선순위가 낮은 작업보다 더 많은 에너지가 필요합니다.

 

앱이 수행하는 작업에 적합한 QoS클래스를 정확하게 지정하면,

 

앱이 반응적이고 에너지 효율이 좋아지는 것을 보장 할 수 있다합니다.

 

참고로 QoS클래스를 지정해주지 않을 경우 기본값인 default 클래스를 갖게됩니다.

 

 

Sync / Async

 

앞서 설명드렸던 바와 같이 동기 / 비동기의 개념으로, 

 

다른 쓰레드에서 작업중인 task가 끝날때까지 대기할 것 인지,

 

다른 쓰레드에게 일이 할당되는 순간 다른 task를 바로 작업할 것인지 결정해주는 구간입니다.

 

 

 

참고한 블로그

https://zeddios.tistory.com/516

https://jeonyeohun.tistory.com/279

'iOS' 카테고리의 다른 글

FCM과 APNs에 관하여  (0) 2022.05.03
CI/CD(feat. Fastlane)  (0) 2021.11.29
Apple 인증서 및 CodeSigning  (0) 2021.11.24