Structured Concurrency
정의
구조적이라는게 무슨 뜻일까 ?? 나는 구조적 = 계층적 이란 느낌이 들었다.
계층 구조의 대표적인 구조는 부모 - 자식 구조로 생각할 수 있다.
작업들 역시 마찬가지다.
특징
- 작업간의 의존성이 생김 , 자식 작업이 모두 끝나야 부모가 끝날 수 있음
- 부모가 자식 작업을 취소할 수 있음 (취소 전파)
- context 상속 , [우선순위, 로컬 변수 등]
- 자식 작업의 우선순위가 부모보다 높아지면, 부모 작업도 같이 높아짐
이전 코드는 동시성일까?
구조적 동시성에 대해 한번 깊게 고민해보자.
아래 그림을 보며 생각해보자.
너무 간단하다 아래 그림이 동시에 실행되는 구조 같다.
우리는 동싱성에 대해 배우고 있는데 그러면 이런식으로 코드를 짰을까 ??
이번에는 우리가 직접 짰던 코드를 봐보자.
func multiFetchDatas() async -> [Data] {
var datas: [Data] = []
do {
let data1 = try await asyncFetchData()
let data2 = try await asyncFetchData()
let data3 = try await asyncFetchData()
datas.append(data1)
datas.append(data2)
datas.append(data3)
} catch {
}
return datas
}
앞에서 구현한 asyncFetchData를 3번 실행해봤다.
하지만 이거는 1번 그림에 가까운 구조다.
async let (동시 바인딩)
그렇다면 구조적 동시성을 하기위해서는 어떤식으로 코드를 짜야할까??
가장 간단하게 구조적 동시성을 만들어주는 방법은 async let을 이용하는 방법이 잇다.
예제 코드
func multiFetchDatas() async -> [Data] {
var datas: [Data] = []
do {
async let task1 = asyncFetchData() // 기다리지 않고 밑의 코드를 실행, 하나의 자식 작업
async let task2 = asyncFetchData() // 기다리지 않고 밑의 코드를 실행, 하나의 자식 작업
async let task3 = asyncFetchData() // 기다리지 않고 밑의 코드를 실행, 하나의 자식 작업
// 방법 1 각자 바인딩
// 각 작업이 끝나면 bind됨
let bind1 = try await task1
let bind2 = try await task2
let bind3 = try await task3
datas.append(bind1)
datas.append(bind2)
datas.append(bind3)
// 방법 2 리스트 바인딩
let datas = try await [task1,task2,task3]
// 방법 3 튜플 바인딩
let (bind1, bind2, bind3) = try await (task1, task2, task3)
datas.append(bind1)
datas.append(bind2)
datas.append(bind3)
} catch {
}
return datas
}
방법 3번이 선호되는 기다림이다. 왜냐하면 task1, task2, task3 는 순차적으로 도착한다는 보장이 없으므로
방법 1,2 번과 같이 순서를 정해놓고 기다리는 방식은 선호되지 않는다.
특징
- 반복문 사용 x = 확장성 x
- 대규모 병렬이 불가
TaskGroup
A group that contains dynamically created child tasks.
동적으로 자식 작업을 생성할 수 있는 객체
@frozen
struct TaskGroup<ChildTaskResult> where ChildTaskResult : Sendable
자식 작업의 결과를 재네릭으로 갖고있는 것을 볼 수 있다.
생성 메서드
with prefix로 시작하는 메서드를 통해 상황에 맞게 사용할 수 있다.
with을 이용한 생성은 continuation에서 깊이 설명했으므로 Discarding만 짚고 넘어가자.
Discard는 버리다라는 의미를 갖고있다.
자식 작업의 리턴타입이 없어 끝날 때 바로 할당 해제를 하기 위한 키워드이다.
그냥 바로 버리기라고 생각하면 편할 것 같다.
부모작업은 위에서 말한대로 자식 작업이 끝나야 끝날 수 있으므로 빨리 버릴수록 더 좋은 성능을 낸다고 볼 수 있다.
예제 코드
생각보다 구조가 간단하다.
- 자식 작업 객체의 리턴 타입
- 최종 부모 작업의 리턴 타입
- 자식 작업 생성
- 비동기 반복문
- 리턴
여기서 중요한 것은 비동기 반복문이다.
비동기 반복문은 우리가 일반적으로 썻던 반복문과 달리 해당 그룹의 자식 작업이 끝나면 반복문이 한번 순회
이후 다시 작업이 끝나면 순회하는 식으로 진행되며 , 먼저 시작한 작업이 먼저 끝나다는 보장은 없다.
특징
- 반복문 사용 가능 = 확장성 o
- 대규모 병렬가능
구조적 동시성과 비구조된 동시성 비교
방법 | 구조 | 특징 | 상위 context 상속여부 |
Task | 비 구조적 동시성 | 작업취소 불가 | O |
Task.detached | X | ||
async let | 구조적 동시성 | 작업취소 가능 | O |
TaskGroup |
참고
'iOS > Swift Concurrency' 카테고리의 다른 글
Task Cancellation (0) | 2024.10.27 |
---|---|
Continuation (0) | 2024.10.25 |
async await (0) | 2024.10.24 |
swift concurrency 등장배경 (0) | 2024.10.24 |
Task (0) | 2024.10.23 |