#Mobile

Swift 6 어때요? (1): 스레드, 블록, 태스크

Swift 6 어때요? (1): 스레드, 블록, 태스크
01

Summary

RxSwift와 Swift Concurrency의 위험한 공존? 스레드, 블록, 태스크의 10년 역사 해부

우리가 작성한 await 코드는 실제로 어떻게 쪼개지고 힙에 저장될까? 로우레벨 SIL로 분석한 Swift Concurrency의 민낯

애플 플랫폼 동시성 모델의 진화 과정을 NSThread부터 GCD Block, 그리고 Swift Concurrency의 Task까지 흐름에 따라 입체적으로 다룹니다. 채널톡 기술 팀이 Swift 6 도입 전 과도기 코드가 실제로 어떻게 안전하게 동작하는지를 바이너리 심볼과 SIL 명령어 레벨까지 파고들어 명쾌히 풀어낸 탐구서입니다.

  • 01NSThread, GCD Block, Swift Concurrency Task로 이어지는 동시성 작업 단위의 근본적인 차이 설명
  • 02GCD의 블랙박스 구조로 인해 불가피하게 발생했던 '스레드 폭발'의 근본 원인 규명
  • 03컴파일러가 async/await 함수를 partial function들로 분리하고 swift_task_alloc을 통해 힙 컨텍스트에 상태를 담는 원리 증명
  • 04resume 시점에 본래의 실행 컨텍스트로 정상 복귀하도록 보장하는 SIL 명령어 hop_to_executor의 비밀 파헤치기
  • 05Swift Concurrency의 핵심 조건인 'Forward Progress' 및 동기식 차단 작업에 따른 데드락 위험성 경고

RECOMMENDATION

Swift 6 도입을 앞두고 있거나 기존 RxSwift 코드베이스와의 동시성 간극을 해결하고자 하는 모바일 아키텍트 및 실무 iOS 개발자에게 깊이 있는 런타임 통찰을 제공합니다.

The Problem

애플 플랫폼에서 RxSwift와 Swift Concurrency가 혼용될 때 컴파일러가 둘 사이의 안전한 상호운용성을 완전히 보장해주지 못하며, GCD 기반의 블록 모델은 대기 상태 시 스레드 폭발을 유발하는 구조적 한계를 가지고 있었습니다.

The Solution

Swift Concurrency는 컴파일러가 await 지점에서 함수를 조각내어 부분 함수로 분할하고, 살아남아야 할 상태값을 힙의 태스크 컨텍스트에 할당하여 스레드를 점유하지 않고 반납할 수 있는 태스크 모델을 도입했습니다.

The Result

바이너리 역어셈블 및 SIL 분석을 통해 RxSwift와 Swift Concurrency가 공존하는 코드에서 스레드 폭발을 방지하고 고정된 스레드 풀 위에서 안전하게 컨텍스트가 전환되는 동작 원리를 하부 레벨에서 증명했습니다.

Trade-off

Swift Concurrency의 고정 크기 스레드 풀 내에서 동기식 대기(blocking)가 발생하면 풀 전체가 마비될 수 있으며, 컴파일러가 이러한 동기 대기 코드를 감지하지 못하므로 forward progress 보장 규칙을 개발자가 엄격히 준수해야 하는 한계가 존재합니다.

03

Key Concepts

Concept · 01

스레드 폭발 (Thread Explosion)

GCD 큐에서 실행 중인 블록들이 동기식 대기나 세마포어 등에 걸렸을 때, 시스템이 코어를 놀리지 않으려고 임시방편으로 스레드를 지속해서 새로 생성하면서 컨텍스트 스위칭 비용과 메모리 점유가 기하급수적으로 증가하는 현상입니다.

  • 블록 안에서 벌어지는 일시 정지 상태를 GCD 큐가 외부에서 인지하지 못하는 구조적 한계에서 기인합니다.
  • 이를 예방하기 위해서는 블록 내부에서 수동으로 대기 동작을 회피해야 하는 수동적인 컨벤션 가이드만 존재했습니다.
Concept · 02

부분 함수 (Partial Function)

Swift Concurrency에서 await 키워드를 기점으로 컴파일러가 원본 비동기 함수를 실제로 잘라내어 바이너리에 생성하는 여러 조각의 개별 서브 함수들을 의미합니다.

  • 컴파일 과정에서 원래 하나의 함수였던 영역이 진입부와 여러 개의 resume/suspend 부분 함수 조각으로 나뉘게 됩니다.
  • 조각난 함수들이 동작을 재개할 때 참조할 로컬 변수 등의 상태 값은 swift_task_alloc에 의해 힙의 태스크 컨텍스트로 강제 이전됩니다.
Concept · 03

hop_to_executor

일시 중단(Suspension)되었던 태스크가 다시 실행(Resume)될 때 자신이 안전하게 속해야 하는 실행 컨텍스트(Executor/Actor)로 스레드를 강제로 전환 및 매핑해 주는 SIL 레벨의 핵심 명령어입니다.

  • 익명 스레드 풀 위에서 놀던 태스크 조각이 재개 지점을 만났을 때, 자신이 @MainActor 혹은 다른 고유 액터 영역에 속해 있는지를 스스로 기억하고 돌아갈 수 있도록 만듭니다.
  • SIL 레벨의 컴파일 시점에 지정되며 실행 시 안전한 데이터 경쟁 배제(Isolation)를 지원하는 중추 역할을 담당합니다.