TIL
[Swift Concurrency] Structured Concurrency 구조적 동시성
Kim Roks
2025. 4. 15. 19:33
Swift Structured Concurrency
Swift의 Structured Concurrency는 비동기 작업을 계층적으로 구조화하여 코드의 가독성과 안전성을 높이는 방식이다. 이 개념 내에서 async let과 TaskGroup은 병렬 처리를 위한 핵심 도구로 사용된다.
1. async let 활용
개요
async let은 고정된 개수의 비동기 작업을 병렬로 실행할 때 사용된다. 선언된 async let은 즉시 실행되며, 이후에 await 키워드를 사용하여 결과를 기다릴 수 있다. 스코프를 벗어나기 전에 반드시 결과를 await해야 한다.
예시 코드
func fetchUser() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "User"
}
func fetchPosts() async -> [String] {
try? await Task.sleep(nanoseconds: 2_000_000_000)
return ["Post 1", "Post 2"]
}
func loadData() async {
async let user = fetchUser()
async let posts = fetchPosts()
let (fetchedUser, fetchedPosts) = await (user, posts)
print("User: \\(fetchedUser), Posts: \\(fetchedPosts)")
}
특징
- 고정된 수의 비동기 작업에 적합
- 코드가 간결하며 사용이 직관적
- await는 반드시 해당 스코프 내에서 수행되어야 함
- 결과는 순서대로 병합 가능
2. TaskGroup 활용
개요
TaskGroup은 동적으로 생성된 복수의 비동기 작업을 그룹화하여 병렬로 실행할 수 있도록 한다. 작업 수가 가변적인 경우에 적합하며, 각 작업의 완료 시점에 따라 결과를 비동기적으로 수집할 수 있다.
예시 코드
swift
복사편집
func fetchItem(id: Int) async -> String {
try? await Task.sleep(nanoseconds: UInt64(1_000_000_000 / id))
return "Item \\(id)"
}
func fetchAllItems() async {
let ids = [1, 2, 3, 4, 5]
var results: [String] = []
await withTaskGroup(of: String.self) { group in
for id in ids {
group.addTask {
await fetchItem(id: id)
}
}
for await result in group {
results.append(result)
}
}
print("Results: \\(results)")
}
특징
- 작업 수가 유동적인 경우에 적합
- 각 작업은 group.addTask를 통해 추가
- for await를 통해 결과를 순차적으로 수집
- 에러 전파와 작업 그룹 전체 취소 기능을 지원
3. async let과 TaskGroup 비교
항목 async let TaskGroup
작업 개수 | 고정 | 유동적 |
코드 복잡도 | 낮음 | 비교적 높음 |
사용 용도 | 간단한 병렬 처리 | 복잡하고 동적인 병렬 작업 |
결과 수집 방식 | 튜플 등으로 동시에 수집 | for await로 순차적으로 수집 |
에러 처리 | try 사용 | try, throwing TaskGroup 지원 |
취소 처리 | 제한적 | 그룹 전체 취소 가능 |
공통점
- 부모 작업이 완료되기 전에 모든 자식 작업이 완료 되도록 기다림(어떤 경우에도 보장됨)
- 자식 작업의 우선 순위가 높아지면 부모 작업의 우선순위 또한 자동으로 높아짐