본문 바로가기
Swift/문법

[Swift] Concurrency 동시성 프로그래밍 1탄 - GCD, 기본개념 이해하기

by iOS조이킴 2024. 6. 23.

안녕하세요 조이킴입니다.🐥

개발자라면 무조건 알아야할 동시성 프로그래밍에 대해 정리해보려 합니다.

내용이 방대하므로 이 페이지에서는 기본적인 동시성 프로그래밍에 대한 개념과 GCD 관련 개념을 정리해보겠습니다. 

 

앱 성능 향상을 위해서는 상황에 맞춰 적절한 동시성 코드를 작성해야 하고, iOS 앱 개발 시에는 애플이 만들어둔 GCD로 동시성 코드를 작성할 수 있습니다. 

즉, GCD를 잘 이해하면 비동기적으로 작업을 실행하고 애플의 멀티코어 프로세서 성능을 최적으로 활용할 수 있게 됩니다.

https://developer.apple.com/documentation/DISPATCH

Thread 스레드 개념

 

프로그램을 실행하여 독립적인 메모리 공간에 올라간 상태를 프로세스라고 하는데, 이 프로세스 내에서 실행 되는 각 작업 단위를 스레드라 합니다.

각 프로세스는 최소 하나 이상의 스레드를 포함하고 있습니다.

 

Multi Threading 멀티스레딩 개념

 

프로세스 내에서 하나가 아닌 여러 개의 스레드가 작업을 동시에 병렬로 분산 처리 하는 것을 의미합니다. 

메인 스레드 혼자 작업을 하는 것이 아닌, 여럿이 동시에 작동하기 때문에 작업 수행 속도가 빠르지만, 언제 어떤 스레드가 먼저 작업을 마칠 지는 알 수 없습니다. 

 

Concurrency 동시성 프로그래밍 개념

프로그램이 여러 작업을 동시에 처리할 수 있도록 설계하는 방법론입니다. (멀티스레딩, 비동기 함수, 이벤트 루프 등의 여러 기법을 포함하는 개념)

네트워크 통신으로 용량이 큰 이미지나 파일을 받는 경우 등 오래 걸릴 수 있는 작업은 동시성 코드 작업을 통해 여러 스레드에서 (concurrent 동시) 비동기로 (Async) 작업을 요청하고, 요청에 대한 응답이 오면 메인 스레드에서 UI를 갱신하는 형태로 구현하는 것이 좋습니다. 

 let url = URL(string: "링크주소")

        DispatchQueue.global().async {
            do {
                let data = try Data(contentsOf: url)
                
                DispatchQueue.main.async {
                    self.imageView.image = UIImage(data: data)
                }
            } catch {
                self.imageView.image = UIImage(systemName: "star")
            }
        }

 

 GCD(Grand Central Dispatch) 개념

swift concurrency가 등장하기 전부터 사용된 애플의 동시성 프로그래밍 API입니다. 2016년에 GCD 기반으로 더 많은 기능이 추가된 OperationQueue도 발표되었습니다. 일반적으로는 아직 GCD를 많이 사용하지만, 작업취소 / 일시중지 / 작업재개 등 GCD에서 구현하지 못하는 추가 기능이 필요한 경우 OperationQueue를 사용하기도 합니다. 

 

dispatchQueue 개념

Queue는 스레드를 분산해주는 대기열이고, iOS에서 사용하는 GCD는 dispatchQueue를 갖고 있습니다. 

dispatchQueue에 작업을 보내면 '알아서' 스레드를 생성하여 작업을 보내 실행하고, 작업이 종료되면 스레드를 제거해줍니다. 

dispatchQueue는 3가지 종류가 있습니다. (Main Queue, Global Queue, Custom Queue)

 

종류 1 : dispatchQueue.main

이곳에 할당된 작업은 오직 메인 스레드 한 곳에서 처리하게되는 직렬(Serial)의 특성을 가집니다. 

코드를 작성할 때 별도의 처리를 하지 않으면 default로 메인 큐에 작업들이 할당되고, 메인 스레드가 독박으로 작업 처리를 하게 됩니다.

* 순서가 가장 중요한 작업은 Main Queue로 보냅니다.

 

종류 2 : dispatchQueue.global()

이곳에 할당된 작업은 여러 스레드로 분산되어 처리하게 되는 동시(Concurrent)의 특성을 가집니다.

* 작업 시간 단축이 가장 중요한 작업은 Global Queue로 보냅니다.

보내는 작업은 중요도 QoS(Quality of Service)에 따라 6종류로 우선순위를 설정할 수 있습니다. 

 

1. userInteractive 

2. userInitiated 

3. default

4. utility

5. background

6. unspecified 

 

dispatchQueue 종류 3 : Custom Queue (Serial Queue / Concurrent Queue)

Default로 Serial 직렬 특성을 가진 커스텀 Queue로 생성되나, Concurrent 동시로 설정 가능합니다. 

// default : Serial 
let customSerialQueue = DispatchQueue(label: "joy")

// Concurrent로 특성 변경 
let customConcurrentQueue = DispatchQueue(label:"joy", attributes: .concurrent) 

// qos 추가 설정
let customeConcurrentQueue = DispatchQueue(label: "joy", qos: .userInteractive, attributes: .concurrent)

 

작업 수행 시점 1: Synchronous 동기

하나의 작업이 완료 되어야만 다음 작업을 시작할 수 있으므로 작업의 순서가 중요한 경우 사용합니다.

DispatchQueue class에서 sync 메소드를 사용하면 작업이 끝날 때까지 해당 Queue가 block됩니다.

 

* 같은 큐로는 sync 로 작업을 보내지 말 것.

DispatchQueue class에서 sync 메소드를 사용하면 작업이 끝날 때까지 해당 스레드는 프리징됩니다. (다음 작업 x) 

해당 작업을 완료하기 위한 코드를 실행하려 했는데 그 또한 프리징된 상태인 동일한 스레드에 sync로 배정이 되었다면?

작업을 완료하기 위한 코드 자체를 실행할 수 없는 프리징 상태이므로 결국 해당 작업은 둘 다 절대 완료될 수 없습니다.

이러한 무한 굴레의 늪에 빠져 앱이 터지는 deadlock이 발생하게 됩니다. 

DispatchQueue.global().async {
	DispatchQueue.global().sync{
    // 작업코드
    }
}


이러한 맥락에서, 메인 스레드에서 Dispatch.main.sync{}를 사용하면 안됩니다.

우리가 입력한 코드는 별도 명시하지 않으면 기본적으로 메인 스레드에서 실행되는데, 이 와중에 Dispatch.main.sync{}를 사용하면 메인스레드로 배정이 됩니다. 프리징 상태인 메인 스레드에 할당되었으므로 해당 작업을 실행할 수도 없고, 해당 작업이 완료되어야 프리징 상태가 풀리기 때문에 교착상태인 데드락이 발생하게 되고 앱이 터집니다. 

 

작업 수행 시점 2: Asynchronous 비동기

하나의 작업이 완료되지 않아도 동시에 다른 작업을 시작하므로 작업의 완료 순서도 보장되지 않지만, 더 빠르게 작업을 처리할 수 있습니다.

 

 

*참고

UI를 그리는 task는 Main Thread 메인스레드가 전담합니다.

-> UIKit의 모든 속성을 Thread-safe하게 설계하면(concurrent 동기 - Async 비동기로 작동하면) 뷰를 그리는 작업을 여러 스레드가 동시에 작업하게 되어 성능 저하와 충돌이 발생할 수 있기 때문에 메인 스레드 한 곳에서 차례대로 (직렬 Serial - 동기 Sync) 작업을 진행합니다. (참고: https://medium.com/@duwei199714/ios-why-the-ui-need-to-be-updated-on-main-thread-fd0fef070e7f)

'Swift > 문법' 카테고리의 다른 글

[Swift] 튜플 Tuple  (0) 2024.01.17
[Swift] 조건문 비교 switch문 vs if문  (0) 2024.01.17

댓글