J
[Swift] GCD 본문
GCD (Grand Central Dispatch)
GCD는 Apple이 제공하는 비동기 작업과 동시성 처리를 위한 저수준 API.
멀티코어 프로세서의 성능을 최대로 활용할 수 있도록 도와주며, 스레드 관리를 자동 처리하여 개발자가 직접 스레드를 생성하고 관리하는 부담을 덜어 줌.
Swift에서는 GCD를 사용하여 백그라운드 작업, UI 업데이트, 파일 입출력, 네트워크 요청과 같은 작업을 효율적으로 처리할 수 있음.
1. GCD의 주요 개념
1.1. 작업 단위: 작업(Tasks)
GCD에서는 모든 작업을 작업 단위로 처리.
이 작업은 비동기로 실행할 수 있으며, 각각의 작업은 클로저 형태로 작성.
GCD는 개발자가 요청한 작업을 큐에 적재하고, 적절한 스레드에서 병렬로 처리.
1.2. 큐: Dispatch Queue
Dispatch Queue는 작업을 순차적 또는 병렬적으로 처리되는 대기열.
GCD에서는 작업을 큐에 적재하고, 큐에 적재된 작업은 GCD가 적절한 시점에 실행.
- 직렬 큐(Serial Queue)
- 작업을 하나씩 순서대로 처리.
- 작업의 순서 보장이 필요할 때 사용.
- 동시 큐(Concurrent Queue)
- 여러 작업을 동시에 병렬로 실행 가능.
- 작업의 완료 순서가 일정하지 않음.
2. Dispatch Queue
2.1. 메인 큐 (Main Queue)
메인 큐는 UI 업데이트와 같이 메인 스레드에서 실행되어야 하는 작업을 처리하는 큐.
메인 큐는 직렬 큐로, 메인 스레드에서 실행되므로 UI와 관련된 작업은 반드시 이 큐에서 실행해야 함.
DispatchQueue.main.async {
// 메인 스레드에서 실행되는 코드 (UI 업데이트 등)
}
2.2. 글로벌 큐 (Global Queue)
글로벌 큐는 GCD에서 제공하는 공유된 동시 큐. 작업의 우선순위를 지정해 여러 작업을 동시에 처리할 수 있음.
GCD는 작업의 우선순위를 설정하여 효율적인 스케줄링을 지원.
DispatchQueue.global(qos: .userInitiated).async {
// 비동기적으로 실행되는 코드
}
- QOS (Quality of Service)
- 작업의 중요도를 나타내는 값.
- `userInteractive`, `userInitiated`, `default`, `utility`, `background` 등의 순서로 우선순위 설정 가능.
2.3. 커스텀 직렬 큐 (Custom Serial Queue)
커스텀 직렬 큐는 개발자가 직접 정의한 큐로, 작업을 하나씩 순서대로 처리할 수 있음.
주로 작업의 순서를 보장해야 할 때 사용.
let customQueue = DispatchQueue(label: "com.example.mySerialQueue")
customQueue.async {
// 직렬 큐에서 실행될 작업
}
2.4. 동기 vs 비동기
- 동기(Synchronous)
- 작업이 완료될 때까지 현재 스레드 블로킹.
- 즉, 작업이 끝날 때까지 다음 작업을 수행하지 않음.
- 비동기(Asynchronous)
- 작업이 백그라운드에서 비동기적으로 실행되며, 현재의 작업이 끝나지 않더라도 다른 작업을 계속 실행할 수 있음.
// 동기
DispatchQueue.global().sync {
print("동기 작업")
}
// 비동기
DispatchQueue.global().async {
print("비동기 작업")
}
3. 메인 큐와 글로벌 큐에서 동기/비동기 작업 처리
3.1. 메인 큐에서 동기 작업 처리
DispatchQueue.main.sync {
print("메인 큐에서 동기 작업")
}
print("다음 작업")
- 메인 큐에서 동기 작업을 실행하면 교착 상태(deadlock)가 발생할 수 있음.
- 메인 스레드에서 실행 중인 코드가 이미 메인 큐를 점유하고 있기 때문에, 메인 큐에서 다른 동기 작업을 실행하려 하면 작업이 완료되지 않고 멈추는 현상 발생.
3.2. 메인 큐에서 비동기 작업 처리
DispatchQueue.main.async {
print("메인 큐에서 비동기 작업")
}
print("다음 작업")
- 메인 큐에서 비동기 작업은 현재 스레드를 블로킹하지 않음.
- 먼저 "다음 작업"이 출력된 후, "메인 큐에서 비동기 작업"이 실행.
- 비동기 작업으로 메인 스레드의 작업이 완료될 때까지 기다리지 않고 바로 다음 코드를 실행.
3.3. 글로벌 큐에서 동기 작업 처리
DispatchQueue.global().sync {
print("글로벌 큐에서 동기 작업")
}
print("다음 작업")
- 글로벌 큐에서 동기 작업을 실행하면 "글로벌 큐에서 동기 작업"이 출력된 후 "다음 작업"이 출력.
- 동기 작업이므로 작업이 완료될 때까지 기다렸다가 다음 코드를 실행.
3.4. 글로벌 큐에서 비동기 작업 처리
DispatchQueue.global().async {
print("글로벌 큐에서 비동기 작업")
}
print("다음 작업")
- 글로벌 큐에서 비동기 작업을 실행하면, "다음 작업"이 출력된 후 "글로벌 큐에서 비동기 작업"이 출력.
- 비동기 작업이므로 글로벌 큐에서 작업이 끝나기 전에 다음 작업이 바로 실행.
4. DispatchGroup
`DispatchGroup`은 여러 개의 비동기 작업을 그룹화하여 관리하고, 그룹 내의 모든 작업이 완료될 때까지 기다릴 수 있도록 해줌.
여러 개의 비동기 작업을 동시에 실행하고, 모든 작업이 완료되었을 때 특정 코드를 실행해야 할 때 유용.
4.1. DispatchGroup 사용 예시
let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
print("작업 1 시작")
sleep(2) // 작업 1 처리
print("작업 1 완료")
}
queue.async(group: group) {
print("작업 2 시작")
sleep(1) // 작업 2 처리
print("작업 2 완료")
}
// 모든 작업이 완료되면 실행
group.notify(queue: DispatchQueue.main) {
print("모든 작업 완료")
}
- `group.notify`는 그룹 내 모든 작업이 완료되었을 때 실행.
- `sleep`은 작업 처리 중인 것처럼 시뮬레이션하기 위해 사용.
4.2. `enter`와 `leave` 사용
`enter`와 `leave`는 `DispatchGroup`에서 비동기 작업을 수동으로 시작하고 완료하는 데 사용.
`enter`는 그룹에 작업을 추가하고, `leave`는 해당 작업이 완료되었음을 알림.
let group = DispatchGroup()
let queue = DispatchQueue.global()
group.enter()
queue.async {
print("작업 1 시작")
sleep(2)
print("작업 1 완료")
group.leave()
}
group.enter()
queue.async {
print("작업 2 시작")
sleep(1)
print("작업 2 완료")
group.leave()
}
group.notify(queue: DispatchQueue.main) {
print("모든 작업 완료")
}
- `group.enter()`로 작업을 추가하고, 작업이 끝나면 `group.leave()`를 호출하여 완료를 알림
- 두 작업이 완료되면 "모든 작업 완료"가 출력.
4.3. leave와 enter 주의 사항
`enter()` 호출 횟수와 `leave()` 호출 횟수가 다르면 디스패치 그룹이 종료되지 않음.
즉, 그룹에 `leave()`를 호출하지 않으면 그룹은 작업이 완료되지 않은 것으로 간주.
5. GCD와 Swift Concurrency 비교
GCD (Grand Central Dispatch) | Swift Concurrency | |
사용 방식 | 클로저와 큐를 사용한 비동기 작업 관리 | `async`/`await`를 사용한 비동기 작업 관리 |
동시성 제어 | `DispatchQueue`, `DispatchGroup`, `Semaphore` 등 | `Task`, `TaskGroup`, `actor` 등을 사용하여 동시성 제어 |
코드 가독성 | 클로저 중첩으로 인해 복잡해질 수 있음 | 동기 코드처럼 비동기 코드를 작성 가능 |
에러 처리 | 수동으로 에러 처리 | 비동기 함수에서 `throws`로 에러 처리 가능 |
성능 | 멀티코어 CPU 활용 최적화 | `Task` 및 `actor`로 경량화된 비동기 처리 |
6. GCD 사용 시 주의 사항
- 동시성 문제
- GCD로 비동기 작업을 처리할 때, 여러 스레드에서 동일한 리소스에 접근할 경우 경쟁 상태(race condition)가 발생할 수 있기에 동기화로 문제를 해결해야 함.
- 메인 스레드에서의 작업
- UI 업데이트는 반드시 메인 큐에서 처리해야 함.
- 비동기적으로 백그라운드에서 작업을 수행한 후, 메인 스레드에서 UI를 업데이트.
6. 정리
GCD는 Swift와 iOS에서 비동기 작업과 동시성 처리를 위해 제공되는 강력한 API.
`DispatchQueue`, `DispatchGroup`, `DispatchSemaphore`와 같은 다양한 기능으로 복잡한 동시성 작업을 효율적 관리 가능.
그러나 GCD의 클로저 기반 코드는 가독성이 떨어질 수 있으며, 이러한 단점을 해결하기 위해 Swift Concurrency가 도입.
- GCD는 고성능 동시성 처리를 지원하지만, 적절한 동기화와 리소스 관리가 필요.
- Swift Concurrency는 코드 가독성을 개선하고 에러 처리를 더 쉽게 할 수 있도록 설계 됨.
'Swift' 카테고리의 다른 글
[Swift] GCD vs Swift Concurrency: 스레드 관리와 성능 (0) | 2024.04.15 |
---|---|
[Swift] Swift Concurrency (0) | 2024.04.15 |
[Swift] ARC (0) | 2024.04.13 |
[Swift] mutating (0) | 2024.04.12 |
[Swift] inout (0) | 2024.04.08 |