#Mobile

Swift 6 어때요? (2): executor, job, hop_to_executor

Swift 6 어때요? (2): executor, job, hop_to_executor
01

Summary

스레드 폭발 방지의 핵심! Swift Concurrency의 심장, Executor 작동 원리 분석

100개 태스크 실험과 런타임 소스코드로 파헤쳐 본 hop_to_executor의 최적화 비밀

본 아티클은 채널톡 iOS 팀의 Swift 6 스터디 결과를 바탕으로, 동시성 모델의 핵심 추상화 계층인 Executor와 관련 명령어의 실질적 구조를 심도 있게 탐구합니다. 컴파일러가 삽입하는 hop_to_executor의 진정한 비용을 산출하기 위해 런타임 내 C++ 스위칭 함수를 낱낱이 해부합니다. 이를 통해 스레드를 늘리지 않으면서 동시성을 극대화하는 Swift Concurrency의 강력한 강점과 비차단 설계의 중요성을 깨달을 수 있습니다.

  • 01Swift Concurrency의 Executor는 물리적인 워커 스레드가 아닌 그 위에 설계된 논리적 추상화 계층입니다.
  • 02Thread.sleep과 Task.sleep 실험을 통해 입증된 global executor의 무조건적인 스레드 제한 메커니즘을 보여줍니다.
  • 03hop_to_executor 명령어는 단순한 스레드 전환 지시가 아니라 상황에 따른 스레드 소유권 인계를 조율하는 장치입니다.
  • 04런타임의 swift_task_switch 내부 동작 분석을 통해 컨텍스트 스위칭이 일어나지 않는 최적의 실행 조건 3단계를 제시합니다.
  • 05비동기 환경 안에서 스레드 차단 코드를 사용할 때 global executor 전체가 마비되는 심각한 가용성 병목 리스크를 경고합니다.

RECOMMENDATION

고성능 iOS 앱을 개발하거나 Swift 6 동시성 모드 마이그레이션을 진행하는 개발자에게 강력히 권장하며, 비동기 코드 작성 시 절대로 Thread.sleep 등 스레드를 직접 블로킹하는 전통적인 차단식 동기 코드를 혼용하지 않도록 설계 가이드를 제공합니다.

The Problem

Swift Concurrency 도입 시 내부 동작을 모른 채 키워드를 암기하여 사용하면, 예기치 못한 스레드 병목이나 최적화 실패에 직면할 수 있습니다. 특히 기존 DispatchQueue 방식의 스레드 폭발(Thread Explosion) 문제와 빈번한 컨텍스트 스위칭 비용에 대한 우려를 해소하는 런타임 및 컴파일러 레벨의 명확한 원리 규명이 필요합니다.

The Solution

Swift 6 컴파일러가 생성하는 SIL(Swift Intermediate Language) 명령어 분석과 런타임 소스코드를 직접 분석하여 스레드 제어 매커니즘을 밝힙니다. 비차단(Non-blocking) 방식과 차단(Blocking) 방식을 비교하는 Task.sleep 및 Thread.sleep 100개 태스크 실험을 실행하여 실증적 데이터를 확보하고, swift_task_switch 함수 내부의 3단계 최적화 조건을 증명합니다.

The Result

비차단 방식의 대규모 작업 분배 시 단 8개의 스레드만으로 100개의 비동기 태스크가 지연 없이 신속하게 분배 및 처리되었으나, 차단 방식 호출 시에는 스레드가 코어 수(12개)에 막혀 대기 시간이 급증하는 현상을 실증했습니다. 또한 hop_to_executor 명령어가 매번 스레드 전환을 유발하는 대신 동일 스레드를 유지하거나 인계하는 최적화를 수행해 비용을 비약적으로 줄임을 규명했습니다.

Trade-off

global executor는 스레드 폭발을 절대적으로 방지하는 구조로 동작하기 때문에, 비동기 문맥 내에서 스레드를 직접 blocking하는 코드가 포함되는 경우 전역 큐의 모든 job이 멈추어 시스템 전체가 병목 상태에 빠질 수 있는 위험이 있습니다.

03

Key Concepts

Concept · 01

Executor

Swift Concurrency 환경에서 비동기 태스크(Job)를 받아서 예약하고 적절한 타이밍에 실행시키는 역할을 정의한 핵심 추상화 프로토콜 계층입니다.

  • 물리적 스레드를 직접 프로토콜 명세에 노출하지 않고 job의 격리 및 스레드 풀 분배를 담당합니다.
  • unownedExecutor를 통해 커스텀 스케줄링을 재정의할 수 있는 확장성을 가집니다.
Concept · 02

hop_to_executor

Swift Intermediate Language(SIL) 수준에서 컴파일러가 태스크의 실행 경계가 바뀔 때(isolation 격리 수준 전환 등) 자동으로 생성해 넣는 실시간 전환 지시어입니다.

  • async 함수의 도입부 및 다른 격리 계층 호출 전후에 컴파일러가 알아서 최적의 이동 연산 형태로 삽입합니다.
  • 매번 스레드를 새롭게 교체하는 고비용의 전이 동작이 아니며 최적화 가능성을 항상 전제로 합니다.
Concept · 03

swift_task_switch

hop_to_executor가 최종 호출되었을 때 실행을 지휘하는 런타임 단의 핵심 C++ 최적화 함수입니다.

  • 현재 executor와 전환할 목적지 executor가 동일 혹은 호환 가능하면 스레드를 유지하고 즉시 다음 처리를 진행합니다.
  • 목적지가 비어 있는 상태라면 다른 스레드로 옮겨가지 않고 현재 사용 중인 물리 스레드가 임무를 낚아채어 실행하는 Thread Assumption을 수행합니다.
  • 목적지가 실시간으로 활성화 상태로 차 있어 충돌 시에만 큐에 enqueue하는 백업 플랜으로 전환 비용을 비약적으로 줄입니다.