본문 바로가기
Swift

Swift: Async, Await, Thread

by songmoro 2024. 8. 12.
728x90

동기, 비동기

동기(synchronous): 앞선 작업이 끝날 때까지 다른 작업들이 기다리는 것

비동기(asynchronous): 앞선 작업에 상관없이 작업을 시작하는 것

 

 

CompletionHandler

작업이 끝난 시점에 수행할 작업으로 비동기 작업에서 작업이 끝나는 시점을 알릴 때 주로 사용

 

completionHandler 콜백 문제점

  1. 중첩될 수록 코드의 가독성이 안 좋아진다
  2. 오류 처리가 어렵고 장황해진다.
  3. 호출을 잊을 수도 있다.
  4. self capture + cycle retain 발생 가능

 

 

Async & Await

컴플리션 핸들러의 장황한 코드 블록과 복잡한 에러 핸들링 문제를 해결하기 위해 Swift 5.5부터 도입된 개념

비동기 코드를 마치 동기 코드처럼 작성할 수 있게 해 준다.(외관상)

 

컴플리션 핸들러 코드

func processImageData2c(completionBlock: (Result<Image, Error>) -> Void) {
    loadWebResource("dataprofile.txt") { dataResourceResult in
        switch dataResourceResult {
        case .success(let dataResource):
            loadWebResource("imagedata.dat") { imageResourceResult in
                switch imageResourceResult {
                case .success(let imageResource):
                    decodeImage(dataResource, imageResource) { imageTmpResult in
                        switch imageTmpResult {
                        case .success(let imageTmp):
                            dewarpAndCleanupImage(imageTmp) { imageResult in
                                completionBlock(imageResult)
                            }
                        case .failure(let error):
                            completionBlock(.failure(error))
                        }
                    }
                case .failure(let error):
                    completionBlock(.failure(error))
                }
            }
        case .failure(let error):
            completionBlock(.failure(error))
        }
    }
}

processImageData2c { result in
    switch result {
    case .success(let image):
        display(image)
    case .failure(let error):
        display("No image today", error)
    }
}

 

async await 코드

func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image

func processImageData() async throws -> Image {
    let dataResource  = try await loadWebResource("dataprofile.txt")
    let imageResource = try await loadWebResource("imagedata.dat")
    let imageTmp      = try await decodeImage(dataResource, imageResource)
    let imageResult   = try await dewarpAndCleanupImage(imageTmp)
    return imageResult
}

 

async

함수 뒤에 async를 통해 비동기라는 걸 명시할 수 있다.

async 코드는 동시 콘텍스트(concurrent context)에서만 실행 가능하다. 즉, 다른 async 함수 내 혹은 Task를 통해 수동으로 동시 콘텍스트를 제공할 때 사용할 수 있다.

 

await

async 함수를 호출하기 위한 키워드.

await 키워드로 마킹된 곳은 potential suspension point(잠재적인 일시 중단 지점)로 지정된다. 해당 함수가 대기 상태가 되면, caller 역시 대기 상태가 될 수 있다.

 

suspend

suspend 될 수 있다는 건 스레드가 다른 동작을 수행할 수 있게 제어권을 넘길 수 있다는 것

 

내부 동작 원리(스레드 관리)

sync

  • sync에서의 스레드 관리
    • 호출 - A함수에서 B함수(sync)를 호출하면, A함수가 실행되던 스레드의 제어권을 B에게 전달
    • 진행 - B함수가 끝날 때까지 해당 스레드는 점유되어서 다른 일을 수행하지 않게 되는 원리
    • 종료 - B함수가 종료되면 A함수에게 다시 스레드 제어권을 반납

 

async

  • async에서의 스레드 관리
    • 호출 - A함수에서 B함수(async)를 호출하면, A함수가 실행되던 스레드의 제어권을 B에게 전달
    • 진행 - B함수는 async이기 때문에 스레드의 제어권을 포기하는 suspend가 가능 (suspend 되면 호출한 A함수도 같이 suspend 됨)
    • suspend - 스레드에 대한 제어권은 system으로 가고 시스템은 스레드를 사용하여 다른 작업을 수행
    • resume - 일시 중단된 비동기 함수 B를 다시 실행하는 단계
    • 종료 - B함수가 종료되면 A함수에게 스레드 제어권을 반납

 

await으로 표시된 코드의 일시 중단 지점은 같은 이유로 스레드 양보(thread yielding)라고도 불린다.

 

await 키워드를 통해 코드 블록이 하나의 트랜잭션으로 처리되지 않을 수 있음을 암시한다.

즉, 함수가 정지(suspend)되고, 다른 작업이 먼저 실행될 수 있기 때문에 일시 중단되는 동안 앱의 상태가 크게 변할 수 있다.

 

직렬, 동시

직렬과 동시는 동기와 비동기와는 다른 개념이다.

 

직렬, Serial

(보통 메인 스레드에서) 분산 처리 시킨 작업을 “다른 한 개의 스레드에서” 처리하는 큐

 

동시, Concurrent

(보통 메인 스레드에서) 분산 처리 시킨 작업을 “다른 여러 개의 스레드에서” 처리하는 큐

 

async vs sync

작업을 보내는 시점에서 기다릴지 말지에 대해 다루는 것

 

concurrent vs serial

Queue(대기열)로 보내진 작업들을 여러개의 스레드로 보낼 것인지 한 개의 스레드로 보낼 것인지에 대해 다루는 것

 

 

스레드, thread

어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다.

일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.

 

thread pool

스레드 풀은 동시에 실행되는 작업을 관리하는 데 사용되는 메커니즘

스레드 풀은 작업을 처리하는 쓰레드들의 집합으로 구성되며, 이 쓰레드들은 작업이 도착하면 해당 작업을 실행하고 다음 작업을 기다리는 역할을 한다.

 

 

In Swift…

쓰레드 풀

쓰레드 풀은 작업자 스레드의 모임으로 시스템에 의해 생성되고 관리된다.

각 작업에 대해 새 스레드를 만드는 대신 스레드 풀은 기존 스레드를 재사용해 효율성을 향상한다.

 

대기열, Queue

실행할 작업은 큐에 배치된다.

스레드 풀은 이 큐에서 작업을 가져와 실행 가능한 스레드에 할당한다.

 

작업자 스레드, Worker Threads

실제 작업을 수행하는 스레드.

스레드 풀에 의해 만들어지고 관리되며 작업자 쓰레드는 큐에서 작업을 수행하고, 실행한 다음 더 많은 작업을 수행할 수 있다.

 

스위프트 자체는 GCD(Grand Central Dispatch)라는 메커니즘을 포함하는 Dispatch 프레임워크를 통해 동시성으로 작업할 수 있는 방법을 제공한다.

GCD는 쓰레드 관리의 많은 로우 레벨 디테일을 추상화한다.

 

 

 

참고

[iOS - swift] Async, Await 사용 방법

[Swift] Actor 뿌시기

[Swift] async / await & concurrency

Thread Pool in Swift

 

 

 

 

 

728x90

'Swift' 카테고리의 다른 글

Swift: Result Builder  (0) 2024.08.13
Swift: 이벤트 큐, 이벤트 핸들러  (0) 2024.08.12
Swift: OperationQueue  (0) 2024.08.12
Swift: XCTest  (0) 2024.08.12
Swift: 정규 표현식  (0) 2024.08.12