[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/Concurrency & GCD

[Concurrency] async / await

inu 2023. 3. 31. 20:13
반응형

기초 개념

스레드와 실행컨텍스트

  • 스레드 : 운영체제 관리하는 실행단위로, 하나의 프로세스 내에서 여러개의 스레드가 동시에 실행될 수 있다. 스레드는 독립적인 스택 및 레지스터를 가지고 있으며 CPU가 실행할 수 있는 최소 단위이다. 스레드는 병렬성을 구현하는데 사용된다.
  • 실행 컨텍스트 : 실행중인 코드의 실행상태를 추상화하는 개념으로, 실행 컨텍스트는 해당 코드의 실행위치, 변수값, 호출스택 등을 저장한다. 비동기적 실행을 구현하는데 사용된다. 각 실행 컨텍스트가 해당 코드의 실행 상태를 저장하고 있어 해당 코드가 중단되고 다시 실행될 수 있다.
    • 예를 들어 함수를 호출하면 해당 함수는 독립적인 실행 컨텍스트를 가진다.
    • Swift에서는 Task로 실행 컨텍스트의 개념을 대체한다.
  • 하나의 스레드는 여러개의 실행 컨텍스트를 가질 수 있기 때문에 하나의 스레드에서 여러 코드 블록이 동시에 실행될 수도 있다. 반대로 여러개의 스레드가 하나의 실행 컨텍스트를 공유할 수도 있다.

async / sync와 concurrent / serial

  • async / sync와 concurrent / serial 는 별개의 개념이고 정말 기본적인 개념인데 매번 헷갈림.
  • async / sync
    • async : 특정 작업을 하러가던 말던 남은 작업 수행함
    • sync : 특정 작업 하러가면 끝날 때까지 기다림
  • concurrent / serial
    • concurrent : 여러 스레드가 나눠서 작업하도록함 (순서보장못함)
    • serial : 한 스레드가 혼자 작업하도록함 (순서보장가능)

async / await 함수가 등장한 배경

1. completion 형태 코드 문제점

  • 먼저 기존에 자주 사용하던 completion 형태의 비동기 처리 코드를 살펴보자.
  • 사진의 이름목록 중 첫번째 사진을 다운로드하고 사용자에게 표시하는 코드이다.
listPhotos(inGallery: "Summer Vacation") { photoNames in
    let sortedNames = photoNames.sorted()
    let name = sortedNames[0]
    downloadPhoto(named: name) { photo in
        show(photo)
    }
}
  • 비교적 간단한 케이스임에도 콜백지옥이 깊어질 것이 상당히 우려되는 형태이다.
  • 이를 극복할 방법은 없을까? (1)

2. GCD의 문제점

  • 기존 GCD는 동시성 작업을 처리할 때 특정 스레드가 block 중이라면 다른 처리해야할 스레드를 새로 불러와서 필요한 작업을 수행한다.
    • 이렇게 처리하는 이유는 지속적으로 코어가 작업중인 스레드를 보유하도록 하기 위함이며, 새로운 작업에 세마포어와 같이 block 중인 스레드를 재개할 수 있도록 돕는 코드가 있을 수 있기 때문이다.
  • 하지만 block된 스레드가 많아지면서 스레드가 계속 생성되면 데드락, 메모리 및 스케줄링 오버헤드를 야기하는 thread explosion를 야기할 수 있다.
    • 메모리 오버헤드 : block된 스레드에서 보유중인 메모리 및 리소스의 축적
    • 스케줄링 오버헤드 : 스레드 변경에 따른 과도한 context switch (성능저하)
  • 이를 극복할 방법은 없을까? (2)

async / await의 등장 및 장점

  • 이러한 (1), (2)의 문제를 해결하기 위해 만들어진 것이 async / await 로 대표되는 Swift의 Concurrency이다.
let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)
  • 이를 사용하면 위와 같이 비동기코드를 동기적으로 작성할 수 있어 콜백지옥 문제를 해결할 수 있다.
  • 또한 Swift의 Concurrency는 최대로 실행되는 스레드가 코어의 개수와 동일하고 스레드간 context switch도 일어나지 않는다.
  • 스레드의 block 대신 작업의 재실행을 추적하는 continuation 객체가 존재한다. context switch를 수행하는 대신 같은 스레드 내부의 continuation 객체만을 전환한다. (context switch 대신 함수 호출 정도의 비용만 소모)
  • 이러한 특성으로 thread explosion 문제도 해결된다.

async / await 의 작업 흐름

  • async / await 의 ‘async’ 역시 일반적인 async와 같은 의미로 async 함수가 실행되면 특정 작업을 하러감과 무관하게 다른 작업을 이어서 수행할 수 있다.
  • async / await에서 async를 호출하면 기존 실행 컨텍스트를 처리하던 스레드는 자유로운 상태가 된다. 즉 작업권을 다른 곳에 넘겨줄 수 있게 된 것이다. 다만 보통은 현재 스레드를 그대로 다음 async 작업처리에 이용하는 것이 효율적이기 때문에 같은 스레드를 사용하는 경우가 많다.
    • 사실 개인적인 생각으로는 어떤 스레드를 사용하는지가 그리 중요한 것은 아니라고 생각한다.
    • 애플이 스레드 관리 작업을 추상화하여 제공하는 만큼, ‘아 이렇게하면 Swift가 다양한 스레드를 활용해 동시적으로 작업을 처리하겠구나’ 정도로 이해하면 충분하다.
  • 현재의 실행 컨텍스트를 그대로 유지하면서 async 함수의 작업을 처리한다.
  • async 함수의 작업이 마무리되면 await 아래의 나머지 코드가 이어서 실행된다.
    • 이 때 주의할 점은 시스템이 스레드를 적당히 분배하면서 처리하는 것이기 때문에 async 함수 호출 전의 스레드와 await 이후 작업의 스레드는 같지 않을 수 있다는 점이다.
    • 앱의 상태도 그 동안 충분히 변할 수 있다.
반응형

'🍎 Apple > Concurrency & GCD' 카테고리의 다른 글

[Concurrency] Continuation  (2) 2023.03.31
[Concurrency] Task  (0) 2023.03.31
[Swift] GCD 정리하기  (2) 2022.05.30
[Swift] Operation Queue  (0) 2022.05.27
[Swift] Actor  (4) 2022.03.08