728x90
Concurrency
- 비동기 및 병렬 코드를 작성하기 위한 방법
- GCD & Completion Handler
- Async & Await
Async & Await 등장 이전
- Completion Handler의 중첩으로 Callback Hell 발생
- Callback: 작업이 완료된 후 실행되는 함수 또는 클로저
- 비동기 작업 결과 처리 혹은 특정 작업 후 추가 동작을 위해 사용
- Completion Handler: 비동기 함수에서 특정 작업이 완료된 후에 실행할 콜백 함수 또는 클로저
- 함수는 작업을 시작한 후 반환 되지만, 클로저는 함수가 반환된 후 호출되기 때문에 클로저를 나중에 호출하기 위해 @escaping 어노테이션을 채택
- Callback Hell 문제점
- 중첩된 들여 쓰기로 인해 가독성 저하
- 코드의 흐름이 직관적이지 않음
- 휴먼 에러 증가
- Completion Handler를 사용하지 않아도 에러 발생하지 않아 누락할 수 있음
- self capture로 인한 retain cycle 발생 가능
- Callback: 작업이 완료된 후 실행되는 함수 또는 클로저
- 스레드 풀을 사용하여 병렬 작업 수행
- 특정 작업이 완료되는 걸 기다리지 않아서 데이터 레이스 발생
- 공유 자원에 대한 접근, 동기화 때문에 race condition 혹은 deadlock이 발생할 수 있어 semaphore, mutex와 같은 동기화 메커니즘을 사용해야 함
Async & Await
- Completion Handler 대신 async, await 키워드를 사용해 좀 더 가독성 높게 비동기 프로그래밍 작성 가능
- 비동기 함수를 동기 함수처럼 작성할 수 있어 가독성 향상
- async 함수는 반드시 에러, 값을 반환해서 컴파일 시점에 오류 판독
- 비동기 함수, 메서드
- 실행 도중 중단될 수 있는 함수, 메서드
- 비동기 함수, 메서드를 정의할 때 async 키워드 사용
- 비동기 함수, 메서드를 사용할 때 await 키워드 사용
- await은 Potential Suspension Point
- 잠재적인 일시 중지 지점
- await은 Potential Suspension Point
- 호출 시
- await 키워드로 async 함수, 메서드를 만나면 스레드 제어권 포기
- 이후 코드는 실행되지 않고 일시 중지(대기)
- 스레드 양보(yielding)
- 시스템이 작업 스케쥴링
- 만약, 더 중요한 작업이 있다면 해당 스레드에서 중요한 작업 수행
- async 함수를 수행할 차례가 되면 resume
- 이 때 작업을 수행하는 스레드는 이전과 다를 수 있음
- await 키워드로 async 함수, 메서드를 만나면 스레드 제어권 포기
- 코드 블럭이 하나의 transaction으로 처리되지 않을 수 있다.
- 일시 중지되는 동안 앱의 상태가 크게 변할 수 있음
Continuation
- 일반적으로 동기 함수 호출에선 함수의 로컬 변수와 반환 주소 및 기타 정보가 스택 프레임에 저장
- 스레드가 함수 호출을 실행하면 새 프레임이 스택 프레임에 푸시되고, 완료하면 팝 된다.
- 스레드는 독립적인 스택 영역을 보유, 힙을 통해 공유 데이터 사용
- 스택은 함수 호출 상태를 저장하기 위해 사용
- 기존의 동시성 프로그래밍을 위해선 여러 개의 스레드 사용이 필요
- 여러 개의 스레드 작업을 번갈아가며 처리할 때 컨텍스트 스위칭 발생
- Concurrency에서는 await을 만나면 해당 시점의 함수 상태를 저장
- 여기서 Continuation이 등장
- Continuation은 현재 함수의 상태(변수, 연산 상태, 호출 위치, …)인 함수 컨텍스트를 저장하는 경량 객체
- 비동기 함수의 실행을 스택과 힙에서 관리
- 스택에는 비동기 함수에 필요하지 않은 로컬 변수 저장, 힙에는 Suspension Point에서 실행하는 데 필요한 함수의 상태를 저장
- 이게 Continuation
- Continuation을 통해 일시정지된 함수의 상태를 추적해 재개할 지점을 알 수 있다.
- Continuation은 힙에 저장되서 스레드간 공유 가능
- 로직
- 사용 될 가능성 있는 변수들을 Continuation 형태로 힙에 저장
- Suspend 되었던 함수가 Resume되면, 스택 최상단 프레임이 해당 함수 프레임으로 교체됨
- 이미 힙에 함수의 상태가 저장되어 있어서 새로운 스택 프레임을 생성하지 않고 교체해도 동작
- 교체가 이루어지면, 이미 생성된 스레드의 재사용이 가능
- 비동기 함수가 어떤 스레드에서 Suspend 되었던 간에, 효율적으로 실행될 수 있는 스레드에서 함수 컨텍스트를 불러와 Resume 가능
- 수행
- 함수가 await을 만나 일시 정지되면, 현재 함수의 상태는 Continuation을 통해 힙에 보관
- 작업이 완료되면 Continuation에서 저장된 상태를 복원해 다시 함수 진행
- 작업이 재개되는 스레드는 이전과 다른 스레드일 수 있다.
- Continuation에 저장된 정보가 있어서 어느 스레드든 작업 재개 가능
- 이와 같은 유연한 스레드 관리 방식이 Concurrency에서 도입된 Cooperative Thread Pool 개념
- Concurrency의 비동기 작업은 스레드 전환이 아닌 Continuation 전환
- 컨텍스트 스위칭보다 비용이 적음
- 단순 함수 호출 비용만 소비
- 컨텍스트 스위칭보다 비용이 적음
- Concurrency는 Task에 대해 스레드를 생성하지 않음
- Thread Blocking
- 만약 비동기 작업이 오래 지속된다면 그동안 스레드는 작업을 기다리고 있음
- A 함수에서 B라는 동기 함수를 호출하면, A 함수가 실행되던 스레드의 제어권을 B 함수에게 전달
- 그래서 B 함수가 끝날 때 까지 해당 스레드는 점유되어 다른 일을 수행하지 못함
- B 함수가 끝나면 A 함수에 스레드 제어권 반환
- Concurrency는 Continuation을 통해 스레드가 blocking 되지 않고, 필요하다면 다른 작업 수행 가능
- 만약 비동기 작업이 오래 지속된다면 그동안 스레드는 작업을 기다리고 있음
GCD, Async & Await 비교
- 우선순위 역전
- 스레드 블록
- 컨텍스트 스위칭
- 스레드 익스플로전
- 휴먼 에러
Task
- 비동기 작업의 단위
- Dispatch Queue와 비교
- 공통점
- 임의의 스레드로 처리
- 차이점
- Task 블록 내부에서 await을 만나면 일시 정지하여 다음 코드를 실행하지 않지만, 내부적으로 스레드는 block 처리되지 않아 다른 일을 처리할 수 있음
- Dispatch Queue는 해당 스레드가 점유하고 작업을 진행 중이면, 스레드는 block 되어 다른 일을 처리하지 못함
- 공통점
Actor
- 동시성 프로그래밍에서 주의할 점인 Deadlock, Race Condition을 해결하기 위한 자료형
- Reference 타입이며, 한 번에 하나의 작업만 Actor의 상태를 변경할 수 있음
- 내부적으로 mutex 처리
- nonisolated 키워드
- actor는 let 키워드로 선언하여 읽기만 가능
- 그로 인해 race condition 보장
- 메소드 앞에 nonisolated를 붙여 사용할 때 Task, await 키워드 없이 접근 제공
- actor는 let 키워드로 선언하여 읽기만 가능
- @MainActor 키워드
- 항상 main에서 동작하는 Actor
- 클래스 혹은 메소드에 채택
728x90
'Swift > Basic' 카테고리의 다른 글
Generic, Protocol (2) | 2024.11.07 |
---|---|
Data Type (0) | 2024.11.07 |
GCD (0) | 2024.10.31 |
Memory Management (0) | 2024.10.31 |
Memory (0) | 2024.10.31 |