yyeonjju / Interview_Questions

0 stars 0 forks source link

Swift의 구조화된 동시성(Structured Concurrency) 제공방법으로의 async-await #19

Open yyeonjju opened 4 months ago

yyeonjju commented 4 months ago



비동기 작업 기존 방법

func makeCrunchyCookie(completion: @escaping ((Cookie) -> Void)) { makeDough { dough in // ✅ (1) self.chillDough(dough: dough) { ripedDough in // ✅ (2) self.bakeCookie(dough: ripedDough, time: bakeTime) { cookie in // ✅ (3) self.drawFace(cookie: cookie) { crunchyCookie in // ✅ (4) completion(crunchyCookie) } } } } }


<br/>

- -> **그래서 swift에서 제공하게된 “구조화된 동시성 (Structured Concurrency)”** 

<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

# async-await
- swift에서 “구조화된 동시성 (Structured Concurrency)”  제공 방법? **async-await**
- async 키워드를 사용해서 비동기적으로 동작하는 함수를 만든다
- 이 때, 이 비동기 함수는 코루틴(`Coroutine`)으로 만들어지는데
> << `Coroutine` >>
Swift 언어에서의 `Coroutine`은 비동기적인 작업을 간편하게 다룰 수 있도록 해주는 기능입니다. Swift의 `Coroutine`은 Swift 5.5부터 async/await 패턴을 도입하여 사용할 수 있게 되었습니다. 이를 통해 비동기적인 작업을 동기적인 코드와 유사하게 작성할 수 있으며, 가독성과 유지보수성이 향상됩니다. 
swift의 async-await와 같은 동작이 Kotlin에서 동시성프로그래밍에서는 `Coroutine`이라는 이름으로  불린다.
CPS(Continuation Passing Style) → Swift에서는 async-await, Kotlin에서는 coroutine

- `Coroutine`은 함수가 동작하는 도중 특정시점에 일시정지(suspend)할 수 있고 resume(다시재개)할 수 있게한다. → 비동기함수가 일시정지(suspend)될 수 있는 지점을 알려주는 장치가 await이다.
- 함수의 이름 뒤에 async 키워드를 붙여 해당 함수가 비동기 함수임을 나타낼 수 있고, await 키워드로 비동기함수가 일시정지되는 지점을 알려준다.

<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

# `Coroutine`으로 만들어지는 async 비동기 함수 
→ 이함수는 비동기적으로 동작할 수 있고 await 키워드를 사용해 비동기 함수의 결과를 대기할 수 있습니다. ( 비동기 함수가 일시정지될 수 있는 지점을 알려주는 장치가 await)

```swift
func makeCrunchyCookie() async throws -> Cookie {
    let dough = try await makeDough() // ✅ (1)
    let ripedDough = try await chillDough(dough: dough) // ✅ (2)
    let cookie = try await bakeCookie(dough: ripedDough, time: bakeTime) // ✅ (3)
    let crunchyCookie = try await drawFace(cookie: cookie) // ✅ (4)

    return crunchyCookie
}







비동기함수의 suspend → resume 과정







비동기함수를 처리할 때 suspend/resume


executor : 스레드 관리를 개발자가 아닌 시스템 자체에서 함


suspension point에서 변할 수 있는 것 세 가지!!




Coroutine - async로 만들어진 비동기 함수는 코루틴(Coroutine)으로 만들어짐. 비동기 작업 & 동시성 프로그래밍을 가능하도록하는 것. 이렇게 만들어진 비동기 함수는 suspend 가능

continuation - 비동기 작업에서 suspend-> resume을 가능하도록하는 것

executor - 비동기 작업을 할 때 suspend→ resume 과정에서 애플이 스레드 관리를 시스템 자체에서 할 수 있도록 하는 것




yyeonjju commented 4 months ago

GCD + completionHandler VS Swift Concurrency( async-await 문법) 다시 한 번 정리

https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md#introduction

✅ GCD & completion handler의 문제점과 Swift Concurrency 의 도입 이유!

기존 비동기 처리의 문제점

```swift // (2a) Using a `guard` statement for each callback: func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) { loadWebResource("dataprofile.txt") { dataResource, error in guard let dataResource = dataResource else { completionBlock(nil, error) return } loadWebResource("imagedata.dat") { imageResource, error in guard let imageResource = imageResource else { completionBlock(nil, error) return } decodeImage(dataResource, imageResource) { imageTmp, error in guard let imageTmp = imageTmp else { completionBlock(nil, error) return } dewarpAndCleanupImage(imageTmp) { imageResult, error in guard let imageResult = imageResult else { completionBlock(nil, error) return } completionBlock(imageResult) } } } } } processImageData2a { image, error in guard let image = image else { display("No image today", error) return } display(image) } ``` ```swift // (2b) Using a `do-catch` statement for each callback: func processImageData2b(completionBlock: (Result) -> Void) { loadWebResource("dataprofile.txt") { dataResourceResult in do { let dataResource = try dataResourceResult.get() loadWebResource("imagedata.dat") { imageResourceResult in do { let imageResource = try imageResourceResult.get() decodeImage(dataResource, imageResource) { imageTmpResult in do { let imageTmp = try imageTmpResult.get() dewarpAndCleanupImage(imageTmp) { imageResult in completionBlock(imageResult) } } catch { completionBlock(.failure(error)) } } } catch { completionBlock(.failure(error)) } } } catch { completionBlock(.failure(error)) } } } processImageData2b { result in do { let image = try result.get() display(image) } catch { display("No image today", error) } } ``` > Result 를 써줬다고 하더라도 completionBlock 에 대한 호출을 누락할 수 있다는 허점은 여전.


> 문법적인 패턴을 도입해 보다 명확하고 안전하게 문제를 해결하고자함 ➡️ > Swift Concurrency (async-await)



# ✅ async-await와 Coroutine - async 키워드를 사용해서 비동기적으로 동작하는 함수를 만들 수 있는데 - 이 때 비동기 함수는 **Coroutine**으로 만들어진다. - **Coroutine**이란? - → 함수가 동작하는 도중 특정 지점에서 suspend(일시정지)할 수 있고 resume(재개)할 수 있다. - → 이 때 비동기 함수가 일시정지(suspend) 될 수 있는 지점을 알려주는 장치가 **await**키워드이다 > ```swift 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/await 비동기 함수를 사용하면 비동기 코드를 마치 동기 코드인 것처럼 작성할 수 있다.



# ✅ sync-await에서의 Continuation이란? - 함수 컨텍스트를 "추적"하는 객체 - await를 사용해서 일시정지(suspend)할 수 있는 비동기 함수에서 suspend→ resume 과정에 필요! - async-await에서는 async로 만들어진 비동기 함수 내부의 각task에 함수 컨텍스트를 추적하는 continuation가 할당된다. - → continuation은 task가 suspend되었을 때 발생 - → continuation 덕분에 task에 다시 스레드 제어권 넘겨서 resume될 때 이를 이용해서 suspension point로 돌아갈 수 있다. > (*스레드 제어권과 suspend-> resume 에 관련해서는 다음 글에 자세히! )



# ✅ GCD와 Swift Concurrency 의 스레드 생성

## GCD를 사용할 때는 비동기 task마다 스레드를 생성 > 📍아직 확실하게는 확인 못한 부분 - task하나 suspend 하고 다른 작업 넘어가면 - -> 스레드 +1 생성

## 그렇다면 과연 스레드 수가 많아지면 더 좋은걸까? no !!디바이스의 cpu 코어수보다 많아지면 실제 용량을 초과해 요구하는 셈이 된다(overcommit) - → 실행중이었던 스레드 block( block된 스레드가 다시 실행도기를 기다리면서 가지고 있는 메모리 및 리소스때문에 `메모리 오버헤드` 가능성) - → 스레드가 과도하게 생성돌 경우 빈번한 컨텍스트 스위칭 으로 인한 과도한 `컨텍스트 스위칭`(`스케줄링 오버헤드` 가능성)

## Swift Concurrency (async-await 문법) 에서는 스레드 관리를 시스템 자체에서 처리해 안전성을 보장하고자한다 (executor개념 등장) - 어떤 비동기 task가 일시정지(suspend)되면 ⭐️ 시스템에게 스레드 제어권 넘겨줌 ⭐️ - -> 시스템이 넘겨받은 작업의 우선순위와 실행하기에 적절한 스레드를 고려하여 task가 resume될 때 스레드 제어권을 다시 넘겨줌 - 함수가 suspend되는 과정에서 추가적인 스레드의 생성 없이도 작업할 수 있도록 - **executor**가 task를 적절한 스레드로 배정하는 과정이 포함되어 있다. - executor : 비동기 작업을 할 때 suspend→ resume 과정에서 애플이 스레드 관리를 시스템 자체에서 할 수 있도록 하는 것 > 여기에 executor에 대한 자세한 내용 나오는 것같은데 너무 생소하고 어려워서 다음에 다시 파보기로.. https://github.com/apple/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md#introduction