iOS/Common

iOS에서의 동시성 처리 방식 - GCD, Operation Queue 그리고 Actor

TDCIAN 2024. 4. 2. 16:04

 

동시성 프로그래밍의 개념

동시성 프로그래밍은 여러 작업을 동시에 실행하여 전체 작업의 처리 시간을 줄이는 것을 목표로 합니다. 이는 컴퓨터의 여러 코어를 활용하여 여러 작업을 동시에 처리하거나, 하나의 코어에서 여러 작업을 빠르게 전환하여 실행하는 방식으로 이루어집니다.

동시성 프로그래밍은 작업의 처리 시간을 줄이는 것 외에도, 사용자 인터페이스의 반응성을 향상시키고, 네트워크 요청과 같은 시간이 오래 걸리는 작업을 백그라운드에서 처리하여 앱의 전반적인 성능을 향상시키는 데 중요합니다.

 

병렬 처리와 동시 처리 차이

(1) 병렬 처리(Parallel Processing)

: 여러 코어를 사용하여 여러 작업을 동시에 처리합니다. 이는 작업의 처리 시간을 줄이는 데 효과적이지만, 작업 간의 데이터 공유와 동기화 문제를 해결해야 합니다.

 

(2) 동시 처리(Concurrent Processing)

: 하나의 코어에서 여러 작업을 빠르게 전환하여 실행하는 것으로, 병렬 처리와 달리 작업 간의 데이터 공유와 동기화 문제가 없습니다. 이는 사용자 인터페이스의 반응성을 향상시키는 데 효과적입니다.

 

iOS에서는 멀티코어 프로세서를 활용하여 병렬 처리와 동시 처리를 모두 지원합니다.

 

 

GCD(Grand Central Dispatch), Operation Queue, Actor

1. GCD(Grand Central Dispatch)

장점

(1) 빠르고 효율적

: GCD는 스레드 생성과 관리를 자동으로 처리하므로, 스레드 안전성이나 동기화 문제에 대해 걱정할 필요가 없습니다. 또한, 시스템 리소스 사용을 최적화하고, 사용 가능한 코어에 작업 부하를 균형 있게 분배합니다.

 

(2) 간단하고 쉽게 통합

: GCD는 Swift와 잘 통합되어 있으며, 클로저를 사용하여 작업을 정의하고 간단한 구문으로 디스패치 큐에 전달할 수 있습니다.

 

단점

(1) 제한적

: GCD는 저수준 API로, 작업을 큐에 넣고 비동기적으로 실행하는 기능 외에는 많은 유연성이나 기능을 제공하지 않습니다. 예를 들어, 작업을 취소, 일시 중지, 재시도할 수 없으며, 작업의 의존성을 지정하거나 작업의 상태를 관찰할 수 없습니다. 

 

(2) 오류 발생 가능

: GCD는 동시성 로직을 관리하고, 데드락, 경쟁 조건, 메모리 누수와 같은 잠재적인 문제를 처리해야 합니다.

 

예시 코드

// 시간이 오래 걸리는 작업을 수행하는 함수
func performSomeTask() -> String {
	// 작업 수행
    return "작업 완료"
}

// UI 업데이트 함수
func updateUI(with result: String) {
	// UI 업데이트
    print("UI 업데이트: \(result)")
}

DispatchQueue.global(qos: .background).async {
	// 시간이 오래 걸리는 작업 수행
    let result = performSomeTask()
    
    // 메인 큐에서 UI 업데이트
    DispatchQueue.main.async {
    	updateUI(with: result)
    }
}

 

2. Operation Queue

장점

(1) 더 높은 제어

: Operation Queue는 작업 간의 의존성을 정의하고, 작업을 일시 중지하거나 취소할 수 있는 기능을 제공합니다. 또한, 작업의 우선순위를 설정하고, 작업의 진행 상태와 완료 여부를 관찰할 수 있습니다.

 

(2) 재사용 가능

: Operation Queue는 작업을 재사용할 수 있어 코드의 유지 보수성을 향상시킵니다.

 

단점

(1) 성능 저하

: Operation Queue는 GCD 위에 구축된 추상화 계층이므로, 추가적인 오버헤드와 성능 저하를 초래할 수 있습니다. 또한, 작업을 정의하고 관리하는 데 더 많은 보일러 플레이트 코드가 필요합니다.

 

(2) Swift와의 호환성 문제

: Operation Queue는 Objective-C 기능에 의존하므로, Swift의 타입 시스템과 문법 상 호환성 문제가 발생할 수 있습니다.

 

예시 코드

// 시간이 오래 걸리는 작업을 수행하는 함수
func performSomeTask() -> String {
	// 작업 수행
    return "작업 완료"
}

// UI 업데이트 함수
func updateUI(with result: String) {
	// UI 업데이트
    print("UI 업데이트: \(result)")
}

class MyOperation: Operation {
	override func main() {
    	// 작업 수행
        let result = performSomeTask()
        
        // 메인 큐에서 UI 업데이트
        DispatchQueue.main.async {
        	updateUI(with: result)
        }
    }
}

let operationQueue = OperationQueue()
let operation = MyOperation()
operationQueue.addOperation(operation)

 

3. Actor

장점

(1) 스레드 안정성

: Actor는 데이터 경쟁 조건을 방지하기 위해 설계되었으며, 한 번에 하나의 스레드만 접근할 수 있습니다. 이는 코드의 안정성을 높여줍니다.

 

(2) 간결한 코드

: Actor는 async await 패턴을 사용하여 코드를 작성할 수 있으며, 이는 코드의 가독성과 유지 보수성을 향상시킵니다.

 

(3) 구조화된 동시성

: Actor는 구조화된 동시성(Structured Concurrency)을 지원하며, 동시성 작업의 수명 주기를 명확하게 관리할 수 있습니다. 작업이 완료되거나 취소될 때 자동으로 리소스를 해제합니다.

 

* 구조화된 동시성(Structured Concurrency)

: 동시성 작업의 수명 주기를 명확하게 관리하고, 작업이 완료되거나 취소될 때 자동으로 리소스를 해제하는 방식을 말합니다. 이는 리소스 관리의 효율성을 높이고, 메모리 누수나 데드락과 같은 문제를 방지하는 데 도움이 됩니다.

Actor는 이러한 구조화된 동시성을 지원하는 새로운 참조 타입으로, 특정 동시성 도메인 내에서 상태를 캡슐화하여 데이터 격리와 Thread Safe한 작업을 보장합니다. Actor는 데이터 경쟁 조건을 방지하고, 안전성과 효율성을 향상시키는 동시에, Swift의 기존 패턴과 기능들이 일치하는 방식으로 동작합니다.

Actor의 주요 특징 중 하나는 내부에 메일박스(mailbox)와 같은 큐를 가지고 있어, 요청이 순차적으로 처리되도록 보장합니다. 이는 동시성 작업이 순서대로 실행되어 경쟁 조건을 방지하고, 작업의 순서를 보장합니다. 또한, Actor는 데이터 격리를 통해 동시에 여러 스레드에서 접근하는 데이터를 보호하며, 이는 데이터 경쟁 조건을 방지하고, 코드의 안정성을 높여줍니다.

 

단점

(1) 학습 곡선

: Actor는 GCD나 Operation Queue에 비해 상대적으로 최근에 출시된 동시성 모델이므로, 초기 학습 곡선이 있을 수 있습니다.

 

(2) Swift 버전 의존성

: Actor는 Swift 5.5 이상에서만 사용할 수 있으며, 이전 버전의 Swift를 사용하는 프로젝트에서는 사용할 수 없습니다.

 

예시 코드

// 시간이 오래 걸리는 작업을 수행하는 함수
func performSomeTask() -> String {
	// 작업 수행
    return "작업 완료"
}

// UI 업데이트 함수
func updateUI(with result: String) {
	// UI 업데이트
    print("UI 업데이트: \(result)")
}

actor MyActor {
	func performTask() async -> String {
    	return performSomeTask()
    }
    
    func updateUI(with result: String) {
    	// UI 업데이트
        print("UI 업데이트: \(result)")
    }
}

let myActor = MyActor()

Task {
	let result = await myActor.performTask()
    myActor.updateUI(with: result)
}

 

 

각 동시성 처리 방식은 앱의 요구 사항과 복잡성에 따라 적절하게 선택될 수 있습니다.

GCD는 간단한 비동기 작업에 적합하며,

Operation Queue는 복잡한 작업과 작업 간의 의존성을 관리하는 데 필요한 유연성과 제어를 제공합니다.

Actor는 개체 지향적인 방식으로 동시성을 관리하고, 코드의 가독성과 유지보수성을 향상시키는 이점을 가집니다.

 

 

정보 출처: phind