-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Swift의 동시성(Concurrency) 프로그래밍
동시성 프로그래밍은 여러 작업을 효율적으로 처리하기 위해 동시에 실행되도록 설계된 프로그래밍 방식입니다. Swift에서는 Grand Central Dispatch(GCD), OperationQueue, **Swift Concurrency(Async/Await)**를 사용해 동시성 처리를 수행합니다.
Grand Central Dispatch(GCD)
주요 개념
DispatchQueue
작업을 실행할 스레드를 관리하는 큐입니다. GCD는 작업 단위를 **
클로저**로 정의하여 큐에 추가하고 적절한 스레드에서 실행합니다.- Serial Queue: 작업을 순차적으로 실행.
- Concurrent Queue: 작업을 동시에 실행하되 완료 순서는 보장하지 않음.
Main Queue
- 앱의 메인 스레드에서 실행되며, UI 작업은 반드시 메인 큐에서 실행해야 합니다.
Global Queue
- 시스템에서 제공하는 기본 동시 큐로, 우선순위별로 여러 개의 큐를 제공.
QoS (Quality of Service)
작업의 중요도와 우선순위를 설정하여 시스템 리소스를 적절히 배분합니다.
예:
.userInteractive,.userInitiated,.background등.
GCD 사용 방법
1. 비동기 작업 실행
DispatchQueue.global().async {
print("비동기 작업 실행")
}
2. 동기 작업 실행
DispatchQueue.global().sync {
print("동기 작업 실행")
}
3. 메인 큐에서 작업 실행
DispatchQueue.main.async {
print("UI 업데이트")
}
4. 작업 지연 실행
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
print("2초 후 실행")
}
OperationQueue와 DispatchQueue의 차이점
Feature | OperationQueue | DispatchQueue -- | -- | -- 레벨 | 객체 지향 방식 (NSOperation) | 함수 기반 방식 작업 정의 | Operation 클래스를 상속받아 작업을 정의 | 클로저로 작업 정의 우선순위 설정 | 작업 간 우선순위 설정 가능 | 작업 자체의 우선순위 설정은 불가능 종속성 설정 | 작업 간의 종속성을 지정할 수 있음 | 종속성 설정 불가능 취소 | 작업을 취소할 수 있음 (isCancelled 속성 활용) | 클로저는 취소 불가능 적용 사례 | 복잡한 작업 스케줄링 및 종속성 관리가 필요한 경우 | 간단한 동시성 처리OperationQueue 사용 예
let queue = OperationQueue()let operation1 = BlockOperation {
print("작업 1 실행")
}
let operation2 = BlockOperation {
print("작업 2 실행")
}
operation2.addDependency(operation1) // 작업 2는 작업 1이 끝난 후 실행
queue.addOperations([operation1, operation2], waitUntilFinished: false)
동시성 프로그래밍에서 발생할 수 있는 문제와 해결 방법
1. Race Condition
- 문제: 두 개 이상의 스레드가 동시에 동일한 자원을 접근 및 수정하려고 할 때 발생.
- 해결 방법:
- 동기화(Synchronization):
DispatchSemaphore또는NSLock사용. - Serial Queue: 하나의 작업만 순차적으로 실행.
- 동기화(Synchronization):
예: Race Condition
var sharedResource = 0
DispatchQueue.concurrentPerform(iterations: 10) { _ in
sharedResource += 1 // Race Condition 발생
}
해결 방법
let lock = NSLock()
var sharedResource = 0
DispatchQueue.concurrentPerform(iterations: 10) { _ in
lock.lock()
sharedResource += 1
lock.unlock()
}
2. Deadlock
- 문제: 두 개 이상의 스레드가 서로의 작업이 끝나길 기다리며 무한 대기 상태에 빠짐.
- 해결 방법:
- 동기 호출 지양: 비동기 호출을 통해 작업 순환 방지.
- Lock 순서 정의: Lock을 항상 동일한 순서로 획득.
예: Deadlock
let queue = DispatchQueue(label: "deadlock.queue")
queue.sync {
queue.sync {
print("Deadlock 발생") // 내부 작업이 끝나길 기다리며 멈춤
}
}
해결 방법
let queue = DispatchQueue(label: "no.deadlock.queue")
queue.async {
queue.sync {
print("Deadlock 해결")
}
}
3. Priority Inversion
- 문제: 낮은 우선순위의 작업이 높은 우선순위 작업에 의해 블록될 때 발생.
- 해결 방법:
- QoS를 적절히 설정하여 우선순위를 관리.
Async/Await
Swift 5.5부터 도입된 Async/Await는 비동기 작업을 더 읽기 쉽고 직관적으로 작성할 수 있도록 설계된 동시성 프로그래밍 모델입니다. 기존의 클로저 기반 또는 콜백 지옥 문제를 해결하며, 비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해줍니다.
Async/Await의 주요 특징
- 코드 가독성: 비동기 작업을 순차적으로 작성 가능.
- 에러 처리: **
try-catch**를 사용해 비동기 함수에서 발생하는 오류를 처리. - 동기 코드와의 자연스러운 통합: 비동기 작업과 동기 작업을 혼합하여 직관적인 코드 작성 가능.
Async/Await 사용 방법
1. 비동기 함수 정의
async키워드를 사용하여 비동기 함수 선언.- 반환 타입은 일반적으로 **
async ->로 표시.
func fetchData() async -> String {
// 네트워크 요청 등 비동기 작업 처리
return "Data fetched"
}
2. 비동기 함수 호출
- 비동기 함수는 반드시
await키워드로 호출. - 호출부는
async환경에서만 가능.
Task {
let data = await fetchData()
print(data)
}
3. 비동기 함수와 오류 처리
throws**를 사용해 비동기 작업 중 발생하는 오류 처리 가능.- 호출부에서
try await키워드 사용.
func fetchData() async throws -> String {
if Bool.random() {
throw URLError(.badServerResponse)
}
return "Data fetched"
}
Task {
do {
let data = try await fetchData()
print(data)
} catch {
print("Error fetching data: \(error)")
}
}
4. 병렬 처리
- **
async let**을 사용하면 비동기 작업을 병렬로 실행 가능.
func fetchImage() async -> String {
"Image Data"
}func fetchUserProfile() async -> String {
"User Profile Data"
}Task {
async let image = fetchImage()
async let profile = fetchUserProfile()let results = await (image, profile) print("Results: \\(results)")
}
5. Task 그룹
- 여러 비동기 작업을 그룹화하여 관리.
func fetchData(for ids: [Int]) async -> [String] {
await withTaskGroup(of: String.self) { group in
for id in ids {
group.addTask {
return "Data for ID: \(id)"
}
}var results = [String]() for await result in group { results.append(result) } return results }
}
Async/Await의 장점
- 가독성 향상: 비동기 작업을 순차적 흐름처럼 작성 가능.
- 에러 처리 통합:
try-catch로 동기 코드와 동일하게 에러 처리. - 병렬 작업 지원:
async let및TaskGroup을 통한 효율적인 병렬 처리.
Async/Await의 주의점
- 호출 컨텍스트 제한: **
await**는 반드시 비동기 환경에서 호출되어야 함. - 하위 호환성 문제: iOS 13 이하에서는 사용할 수 없음.
- 적절한 사용 필요: 모든 비동기 작업에 적용하기보다 적합한 경우에만 사용.
Async/Await는 비동기 작업을 보다 쉽게 관리할 수 있도록 해주는 강력한 도구이며, 동시성 코드를 작성할 때 기본적인 선택이 되고 있습니다.