개요
await로 비동기 메소드를 호출하는 경우, Potential Suspension Point로 지정된다.

생각해보면 당연하다
await로 URLSession의 data 비동기 메소드를 호출하면 그 아래 작업들은 data 메소드가 끝날 때 까지 기다리게 된다.
이 지점을 Suspension Point 라고 한다.
이를 통해 fetchThumbnail의 메소드는 더 이상 할 일이 없으니, 해당 작업을 처리하던 스레드가 다른 동작을 할 수 있게끔 제어권을 놓아주는 행위를 할 수 있다.
스레드를 멈추는 것이 아닌, 다른 작업을 할 수 있게 제어권을 넘기는 것 말이다.
Suspend 된다 = 해당 스레드에 대한 제어권을 포기한다
라고 봐도 무방할듯
스레드 제어권 관점
sync 에서의 스레드 제어권
A 함수에서 B라는 sync 동기 함수를 호출하면, A 함수가 실행되던 스레드의 제어권을 B 함수에게 전달한다.
A 함수는 동기적으로 호출했기 때문에 B가 끝날 때까지 아무것도 하지 못 한다.

따라서 하나의 스레드에서 A가 동작하다가 B 작업을 하고, B가 끝나면 A 작업으로 다시 돌아온다.
이게 sync의 스레드 점유권의 흐름이다.
async 에서의 스레드 제어권
개요에서 말했듯 A 작업을 하다 B라는 비동기 메소드를 호출하면 A는 스레드 제어권을 B에게 넘겨준다.
왜냐하면 A 작업은 어차피 B를 호출한 시점부터 그 아래 코드들은 B가 끝날 때까지 아무것도 못 하기 때문이다.

그러면 Suspension Point를 만난 순간부터 스레드 제어권을 포기하면,
해당 스레드에 대한 제어권은 시스템에게 가고 시스템은 스레드를 사용해서 다른 작업을 수행할 수 있게 된다.
우선순위에 따라 여러 작업을 멀티 스레드로 처리할 것이다.
그러다 멈췄던 내 작업이 가장 중요하다고 판단되는 순간에 **해당 함수를 재개(resume)**하고, 비동기 함수는 할당받은 스레드를 다시 제어하며 작업할 수 있게 된다.
- await로 async 함수를 호출하는 순간(= Suspension Point) 해당 스레드 제어권 포기
- async 작업 및 같은 블록 아래의 코드들은 실행 불가능
- 스레드 제어권을 시스템에게 넘기면서, 1번의 호출된 async도 스케줄 대상이 됨
- 시스템은 작업 우선순위를 따지며 작업들을 처리하고, 이때 1번이 실행되던 스레드에서 다른 작업을 먼저 실행할 수도 있음
- 그러다 1번의 호출된 async 작업이 중요해지는 순간(= 내 차례) 다시 작업하라고 resume을 하고, 이 때 특정 스레드의 제어권을 줘서 마저 실행이 된다.
- 중요한 건 이때 Resume되는 스레드는 1번 스레드와 다를 수 있음
await한다고 무조건 Suspension Point가 되는 건 아니지만,
위처럼 await 키워드를 통해 코드 블럭이 하나의 트랜잭션으로 처리되지 않을 수 있음
스택 프레임의 변화
sync 방식의 Stack Frame
모든 스레드는 함수 호출을 위한 자신만의 독립된 스택 영역을 갖는다.

스레드가 함수 호출을 실행하면 새 프레임이 스택에 푸쉬,
해당 스택 프레임은 스택의 Top에 쌓이고 이에는 로컬 변수, 리턴 주소값 등이 포함되어 있다.
쌓인 스택 프레임은 함수가 끝나면 Pop 되어 사라진다.
async 방식의 Stack Frame

- 비동기 메소드인 updateDatabase를 호출
- updateDatabase 내에서 비동기 메소드인 add 호출
- add 내에서 비동기 메소드인 database.save 호출
Flow는 위와 같다.
add가 호출된 상황
먼저 2번, add가 호출된 상황부터 보면
스택 메모리 관점에서는 add 메소드가 호출됐으니 add 메소드에 대한 스택 프레임을 스택 영역에 적재한다.
중요
이때, add 스택 프레임에는 사용할 필요가 없는 Local 변수를 저장한다.
무슨 뜻이냐면, suspension point 때문에 사용되지 않을 (= await 아래) 지역 변수를 스택 프레임 저장한다는 것이다.
그럼 위 사진과 같이 (id, article)이 스택 프레임에 담기게 될 것이다.
왜 이렇게 하냐면, await 전/후로 모두 사용되는 정보를 저장하기 위한 공간이 필요하다.
그럼 await 전에 존재하는 지역변수(= 파라미터) newArticle은 따로 저장 공간이 필요할 것이다.
Suspension Point를 만나서 다른 스레드로 작업을 이어갈 때, 이 전 내용들을 기억하기 위해 Heap 메모리 영역에 저장한다.

위와 같이 말이다.
await database.save가 호출된 경우
이어서 3번, add 함수에서 await database.save를 호출한 경우 이 곳이 Suspension Point가 된다.
그러면 스택 영역 제일 위에 있던 add 스택 프레임의 변화를 보자
이론 상, A 메소드가 호출되다가 B 메소드를 호출하면 A 스택 프레임 위에 B가 쌓이게 된다. 그러면서 B 메소드가 동작이 끝나면 다시 A로 돌아와서 기존 작업을 한다.
그러나, 비동기 메소드에서 비동기 메소드의 경우 add 스택 프레임 위에 쌓이는 것이 아니라, add 스택 프레임이 save 스택 프레임으로 대체된다.
중요
이렇게 동작하는 이유는 await 전후로 사용될 코드가 Heap 영역의 async frame에 저장되어 있기 때문에 스택에 필요하지 않는 것이다.
그리고 스택에 있어봤자 스레드 점유권을 다시 얻을 때 해당 스레드로 돌아온다는 보장이 없기 때문이다.
Suspension Point에서 모든 정보가 Heap에 저장되니, 다시 점유권을 얻었을 때 작업 수행이 가능한 것이다.
이 async 프레임 목록은 Continuation에 대한 런타임의 표현이다.

따라서 3번이 끝나면, 위 사진처럼 save 스택 프레임이 add 스택 프레임을 대체하게 된다.
save 메소드 동작 중 await

save 메소드 내부에서 만약 await로 비동기 메소드를 호출해서 Suspend가 되었다고 가정하겠다.
그러면 해당 스레드는 스레드 점유권을 내주고 되고 해당 스레드는 다른 작업을 수행할 수 있게 된다.
save 메소드 종료 후 Return 과정
save 메소드가 동작할 차례가 되어 continuation에 의해 Heap에 있던 save 비동기 프레임이 스택에 쌓이게 되고,
save 메소드 수행 후 작업을 마치면 [ID]를 반환한다.

save 메소드의 동작이 끝나면 [ID]를 반환하고, save를 위한 스택 프레임은 add 메소드를 위한 스택 프레임으로 대체된다.
Continuation
위 사진을 보다보면 continuation이라는 키워드가 나왔는데, 이는 단순히 비동기 호출 후 일어나는 일이다.
await 호출 아래에 있는 모든 것은 continuation 이다.
이를 통해 메모리 레벨에서 컨텍스트 스위칭이 발생하니 훨씬 시간 절약이 좋은 것.
복습
- Await를 통해 비동기 요청을 했을 경우를 Suspension Point라고 한다.
- 동일한 메소드 내에서 Suspension Point 전/후의 코드는 동일한 스레드라는 보장이 없다.
- 이는 스레드 제어권을 시스템에 넘겨주기 때문이다.
- 스택 프레임을 Heap 영역의 async frame 목록에 저장하고 있기 때문에 가능하다.
https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f
Swift concurrency: Behind the scenes - WWDC21 - Videos - Apple Developer
Dive into the details of Swift concurrency and discover how Swift provides greater safety from data races and thread explosion while...
developer.apple.com
'📱 iOS > Swift Concurrency' 카테고리의 다른 글
[Swift Concurrency 9편] Sendable 프로토콜 (0) | 2025.02.26 |
---|---|
[Swift Concurrency 8편] Task와 구조화된 동시성(= Structed Concurrency) (0) | 2025.02.26 |
[Swift Concurrency 6편] Swift Concurrency 등장 배경 (1) | 2025.02.26 |
[Swift Concurrency 5편] 동시성 프로그래밍 with GCD (0) | 2025.02.25 |
[Swift Concurrency 4편] 직렬-동시, 동기-비동기 (0) | 2025.02.24 |