XNU 커널과 GCD·Swift Concurrency Who actually makes the kernel thread?
DispatchQueue.global().async {} 한 줄이 실제로 커널까지 내려가 스레드를 만드는(또는 재사용하는) 경로”를 코드 레벨로 추적한다.이 페이지는 전부 ⊕ 교재 외 확장입니다. 4장 본문 §1(스레드 해부)·§3(사용자/커널 스레드와 M:N 매핑 모델)·§5.4(GCD의 큐·QoS 4클래스)에서 이미 다룬 개념 비교·매핑 모델·GCD API 표면은 여기서 반복하지 않고, 그 아래 커널 레벨만 다룹니다. Swift의 actor·Sendable·구조적 동시성 문법은 Swift Concurrency 코스로 넘깁니다.
⊕ 교재 외 확장 현대 애플 런타임·커널 스케줄러·저수준 SPI
“GCD가 앱 시작 시 코어 수만큼 커널 스레드(KT) 풀을 미리 다 만들어 둔다”는 설명은 부정확하다. 실제로는 사용자 공간의 libdispatch는 스레드를 직접 만들지 않는다 — 커널의 workqueue 서브시스템에 “일꾼 스레드가 필요하다”고 요청만 하고, 실제 생성·캐싱·재사용 결정은 커널이 시스템 부하를 보고 내린다. 아래 §8.4가 이 경로를 정확히 추적한다.
한 문장으로: 당신은 “작업(Task/block)”을 제출하고, libdispatch는 “스레드 달라”고 커널에 요청하며, 실제 커널 스레드를 만들고 재사용하는 결정권자는 XNU의 workqueue 매니저다. 개발자→libdispatch→커널의 세 층이 각각 무엇을 책임지는지가 이 부록의 뼈대다.
8.3XNU의 이중 구조 — BSD proc + Mach task/thread ⊕ 확장
macOS·iOS의 커널 XNU(“X is Not Unix”)는 두 혈통의 합성이다: 최하층의 Mach 마이크로커널(태스크·스레드·가상 메모리·IPC 담당)과 그 위의 BSD 층(POSIX API·프로세스 개념·파일 시스템·시그널 담당). 그래서 하나의 “프로세스”·“스레드”가 두 개의 구조체로 이중 표현된다.
마이크로커널(microkernel): 스케줄링·메모리·IPC 같은 최소한의 핵심만 커널(특권 모드)에 두고, 파일시스템·드라이버·네트워크 스택은 사용자 공간 서버로 빼는 설계. 반대인 모놀리식(monolithic)은 그 전부를 커널 안에 둔다(Linux). 마이크로커널은 모듈성·안정성(한 서버가 죽어도 커널은 산다)이 높지만, 기능마다 서버 간 IPC 왕복이 생겨 느리다.
왜 XNU는 “하이브리드”인가: 순수 Mach 마이크로커널은 BSD 기능을 별도 서버로 두면 IPC 오버헤드가 크다. 그래서 XNU는 성능을 위해 BSD(파일·시그널·POSIX)를 사용자 공간 서버가 아니라 커널 주소 공간 안에 함께 넣었다 — 마이크로커널의 구조 + 모놀리식의 성능을 절충한 하이브리드 커널. 위 §8.3의 proc(BSD)+task(Mach) 이중 표현이 바로 이 절충의 흔적이다.
Mach 포트(port)와 IPC: Mach의 모든 통신은 메시지 전달이고, 그 통로가 포트다 — 커널이 관리하는 단방향 메시지 큐 + 능력(capability) 기반 권한(그 포트로 보낼/받을 권리를 가진 task만 접근). 표의 “task의 포트 권한”이 이것이며, 프로세스 제어·예외(exception) 처리·드라이버 통신이 전부 포트 위에서 돈다. 4장 §9의 액터 모델(사유 상태 + 우편함 메시지)과 같은 정신이 커널 최하층에 박혀 있는 셈이다.
| 개념 | Mach 층 (스케줄링·메모리) | BSD 층 (POSIX·시스템 콜) |
|---|---|---|
| 프로세스 | task — 주소 공간(vm_map)·포트 권한·스레드 목록의 컨테이너. 스스로 실행되지 않는다(자원 보유자). | proc — PID·자격증명·파일 디스크립터·시그널 상태 |
| 스레드 | thread(thread_t) — 실제 스케줄링 단위. 레지스터 상태·커널 스택·우선순위·run state | uthread — 시스템 콜 처리 중의 BSD측 스레드 상태(errno, 시그널 마스크 등) |
핵심은 Mach task는 자원 컨테이너일 뿐, CPU에 올라가는 것은 언제나 Mach thread라는 점이다. 이는 4장 §1의 “프로세스는 자원, 스레드는 실행 흐름” 구분이 커널 구조에 그대로 박제된 형태다.
proc+Mach task로, 스레드를 Mach thread+BSD uthread로 이중 표현한다. Linux의 task_struct(§8.1)·Windows의 ETHREAD/KTHREAD/TEB(§8.2)와 나란히 놓이는 세 번째 모델이며, 모두 1:1(사용자 스레드 하나 = 커널 스레드 하나)이다.Linux: 프로세스도 스레드도 task_struct 하나, 공유 정도만 다름. Windows: 커널 KTHREAD + 사용자 TEB. XNU: 커널 Mach thread + 사용자 pthread. 표현은 달라도 결론은 같다 — 오늘날 데스크톱·모바일 OS는 전부 커널이 직접 스케줄하는 1:1 커널 스레드 위에 서 있다(§3.1의 “1:1이 이겼다”).
8.4pthread_workqueue — GCD는 커널에 스레드를 “요청”만 한다 ⊕ 확장
4장 §5.4는 GCD를 큐·QoS·main queue 같은 API 표면에서 다뤘다. 그 아래에서 실제 스레드는 누가 만드는가? 답의 핵심은: libdispatch(사용자 공간)는 pthread_create를 직접 부르지 않는다. 대신 커널의 workqueue 서브시스템에게 일꾼 스레드를 요청하고, 생성 여부는 커널이 결정한다.
※ 아래 등장하는 함수들은 공개 API가 아니라 SPI(System Programming Interface — 애플 내부/시스템 라이브러리 전용 비공개 인터페이스로, 앱에서 직접 호출하면 심사에서 거부된다)다. 여기서는 “libdispatch가 커널과 어떻게 대화하는가”를 보여주는 개념 예시로만 쓴다.
/* (1) 부팅 시 딱 한 번: "워커 스레드의 진입점은 이 함수다" 라고 커널에 등록 */
pthread_workqueue_setdispatch_np(_dispatch_worker_thread);
/* ↑ 실제 생성이 아니라 '콜백 등록'일 뿐 */
/* (2) 큐에 일이 쌓였고 처리할 스레드가 부족할 때: "N개 더 줘" 라고 요청 */
pthread_workqueue_addthreads_np(priority, /*numthreads=*/1);
/* ↑ 이 호출은 syscall로 커널에 내려가고,
실제 pthread(=Mach thread) 생성은 커널의 workqueue 매니저가 수행 */과거 pthread는 개별 작업 항목을 커널에 넘기는 _np API(additem류)로 스케줄링까지 관여할 수 있었지만, 현대 XNU에서는 그 권한이 커널로 이동했다. libdispatch는 “스레드 몇 개 필요”만 말하고, 어떤 작업을 어느 스레드에 태울지·몇 개를 실제로 만들지는 커널이 시스템 전체 관점에서 정한다.
스레드 풀 크기를 사용자 공간에서 정하면 libdispatch는 자기 프로세스만 본다 — 다른 앱의 부하, 전체 CPU 압박, 메모리 압박, 배터리 상태를 모른 채 “몇 개 만들지”를 추측할 수밖에 없다. 커널이 정하면 실시간 시스템 부하를 보고 “지금은 스레드를 더 안 만든다 / 만든다”를 전역 최적으로 결정할 수 있다. GCD의 설계 철학 — “스레드 풀 관리를 개발자 손에서 떼어 OS에 최대한 가깝게 둔다” — 이 커널 workqueue로 귀결된 이유다.
8.4.1 DispatchQueue.global().async 한 줄의 전체 경로
8.4.2 Overcommit — 코어 수를 넘겨 만들 때
일반(non-overcommit) 글로벌 큐에서 커널은 대략 “동시에 실행 가능한 워커 수 ≒ 활성 코어 수”를 상한으로 삼는다 — CPU를 실제로 쓰는 작업이라면 코어보다 많은 스레드는 문맥 교환 낭비이기 때문이다. 그런데 워커가 블로킹 시스템 콜(디스크 I/O, 락 대기)에 걸리면 그 스레드는 CPU를 안 쓰면서 자리를 차지한다. 이때 다른 작업이 굶지 않도록 커널은 overcommit 정책으로 코어 수를 넘겨 새 워커를 만든다.
수백 개의 블록을 전부 블로킹하는 작업으로 글로벌 큐에 쏟아부으면, 커널은 “막힌 워커」를 보충하려 overcommit으로 계속 새 KT를 만든다 → 수십~수백 개 KT가 뜨고 각자 스택(수백 KB~수 MB)을 잡아 메모리·스케줄링이 무너진다. 해법은 스레드가 아니라 동시성의 폭을 제한하는 것: 직렬 큐로 직렬화하거나, DispatchSemaphore/maxConcurrentOperationCount로 동시 실행 수를 묶거나, 애초에 블로킹을 Swift Concurrency의 await(스레드를 붙잡지 않고 suspend)로 바꾼다(§8.7).
8.4.3 왜 이렇게까지 — 큐잉 15명령 vs 스레드 생성 수백 명령
GCD로 작업 하나를 큐에 넣는 데는 원자적 큐 삽입 약 15개 명령이면 충분한 반면, 전통적 pthread_create는 커널 진입 + 스택 매핑 + 자료구조 할당으로 수백 개 명령이 든다. 그래서 GCD의 규칙은 “스레드를 만들지 말고 작업을 제출하라”이며, 만들어진 KT는 커널이 캐싱해 재사용한다 — 매번 새로 만드는 Thread{}(§8 본문의 NSThread 안티패턴)와 정반대다.
현대 macOS/iOS의 libdispatch는 kqueue/kevent(커널이 다수 소켓·타이머·파일 이벤트의 준비 상태를 한 번에 통지하는 메커니즘, §9.1의 netpoller와 같은 계열)와 workqueue를 하나로 통합했다. 이벤트가 준비되면 커널이 곧바로 적절한 워커를 깨우거나 만들어 그 이벤트 핸들러를 태운다 — 폴링 없이, 유휴 스레드를 붙잡지 않고. 즉 “스레드 풀”과 “이벤트 루프”가 커널 안에서 합쳐진 work-stealing·이벤트 구동 구조다.
8.5Clutch 스케줄러 — QoS를 실제 실행 순서로 바꾸는 곳 ⊕ 확장
커널이 워커 스레드를 만들었다면, 어느 스레드를 먼저, 그리고 어떤 코어에 올릴지는 스케줄러가 정한다. 현대 XNU는 Clutch 스케줄러를 쓰는데, 이는 애플의 비대칭 멀티코어(AMP)를 잘 다루려고 다듬어진 설계다(§8.5.1). 4장 §5.4에서 개발자가 지정한 QoS(.userInteractive … .background)가 여기서 실제 우선순위로 번역된다.
Clutch의 발상: 개별 스레드가 아니라 스레드 그룹(같은 목적으로 협력하는 스레드들, 예컨대 한 앱의 한 QoS 대역)을 스케줄 단위로 본다. 그리고 각 그룹에 interactivity score를 매긴다.
interactivity_score ∝ 자발적 블로킹 시간(voluntary blocking)
───────────────────────────────────────
CPU 사용 시간(CPU usage)
· 자주 자발적으로 블록(입력·네트워크 대기) → 점수 높음 → "상호작용형"
· CPU만 오래 태움(계산·디코딩) → 점수 낮음 → "배치형"| 스레드 그룹 | CPU 사용 | 자발적 블로킹 | interactivity | 스케줄러 취급 |
|---|---|---|---|---|
| UI/터치 반응 | 10ms | 90ms | 높음 | 우선 배치 |
| 이미지 디코딩·배치 | 100ms | ~0ms | 낮음 | 상대적으로 밀림 |
효과: 별도 설정 없이도 “터치에 반응하는 스레드”가 “백그라운드 계산 스레드”보다 자연스럽게 우선한다. 예컨대 이미지 라이브러리가 다운로드·디코딩(CPU 집약, 낮은 interactivity)을 도는 동안에도 메인 스레드의 UI 갱신(높은 interactivity)이 부드럽게 끼어드는 이유가 이 설계다 — QoS는 “의도”만 표현하고, 실제 반응성은 커널이 실측 행동으로 보정한다.
8.5.1 AMP — 왜 비대칭 코어가 Clutch를 부르나 ⊕ 확장
4장 §2.3의 멀티코어는 대칭(SMP)이었다 — 코어가 전부 같아, 스케줄러는 “어느 코어든” 올리면 됐다. 그러나 애플 실리콘·모바일 SoC는 비대칭 멀티코어(AMP, asymmetric multiprocessing)다: 성능은 높지만 전력을 많이 먹는 P코어(Performance)와, 느리지만 전력 효율이 좋은 E코어(Efficiency)가 섞여 있다(ARM big.LITTLE의 계보). 그래서 스케줄러에 새로운 질문이 생긴다 — “먼저 실행할까”뿐 아니라 “어느 종류의 코어에 올릴까?”
이제 8.5의 interactivity score가 왜 중요한지가 분명해진다: 대칭 코어라면 “순서”만 정하면 되지만, 비대칭 코어에서는 “어느 코어”까지 정해야 하고, 그 판단 기준이 그룹의 상호작용성이다. 상호작용형(UI·입력)은 P코어로 올려 지연을 줄이고, 배치형(디코딩·동기화)은 E코어로 밀어 배터리를 아낀다. 반응성과 전력을 동시에 잡는 이 균형이 모바일에서 특히 중요하다.
엄밀히는, 코어 선택까지 포함해 AMP를 본격적으로 다루는 부분을 애플은 Edge 스케줄러라 부르며 Clutch의 확장·후속으로 도입했다. 실무 관점에서는 “Clutch 계열의 그룹 기반·interactivity 기반 스케줄링이 AMP 코어 배치까지 책임진다”로 이해하면 충분하다.
8.6Turnstile — 우선순위 역전을 막는 다단계 전파 ⊕ 확장
QoS가 있으면 새 문제가 생긴다: 우선순위 역전(priority inversion). 높은 QoS 스레드가, 낮은 QoS 스레드가 쥐고 있는 락을 기다리는데, 그 낮은 스레드가 CPU를 못 받아 락을 못 놓으면 → 높은 스레드도 무한정 대기한다.
T_high(userInteractive)가 락 대기 → 락 보유자는 T_low(background) → 시스템에 T_mid가 많으면 T_low가 스케줄을 못 받음 → 락이 영원히 안 풀림 → 사실상 T_high가 T_mid보다 낮은 우선순위로 전락(역전).
XNU의 turnstile이 이를 우선순위 상속(priority inheritance)으로 푼다: 대기 중인 스레드의 우선순위를 락 보유자에게 임시로 전파(propagate)한다. T_low가 잠깐 T_high의 우선순위로 승격 → 빨리 실행해 락을 놓음 → T_high가 즉시 진행. 이를 위해 커널 스레드는 우선순위 큐를 둘 유지한다.
struct thread {
int base_pri; /* Base priority — 당신이 지정한 QoS가 번역된 값 */
int sched_pri; /* Scheduled priority — turnstile 부스트 등이 반영된 '실제' 실행 우선순위 */
...
priority_queue base_inheritor_queue; /* 유저측 상속(예: 우선순위 지정 SPI) */
priority_queue sched_inheritor_queue; /* 커널측 상속(turnstile 부스트) */
};
/* 두 값이 다를 수 있다: base_pri = utility(낮음), sched_pri = userInteractive(임시 부스트) */8.6.1 Multi-hop — GCD가 특히 필요로 하는 것
GCD에서는 대기 사슬이 한 단계로 끝나지 않는다. 예를 들어 DispatchQueue.main.sync { … }로 메인 스레드가 어떤 큐를 기다리는데, 그 큐의 블록이 실행되려면 새 워커 스레드 생성이 끝나야 하고, 그 생성은 또 다른 스레드가 담당한다. Turnstile은 이 사슬을 따라 우선순위를 여러 단계로 연쇄 전파한다.
block.wait()보다 락 공유가 나은가dispatch_block_wait()는 QoS override 위에 구현돼 multi-hop이 아니다(1단계 전파만). 앱에서는 메인 스레드가 최고 우선순위라 대개 1-hop이면 충분하지만, 더 깊은 사슬에서 확실한 우선순위 전파를 원하면 락(뮤텍스) 기반 대기가 낫다 — 커널 turnstile이 락 소유권 사슬을 따라 multi-hop 부스트를 제대로 걸어주기 때문이다. “가능하면 sync+락, 저수준 wait 남용은 피하라”는 조언의 커널 근거가 이것이다.
8.7Swift Concurrency의 Task ↔ 커널 스레드 — 근본적 비대칭 ⊕ 확장
지금까지가 커널 스레드(무거운 것)의 이야기였다. Swift Concurrency의 Task는 그 위에 얹힌 가벼운 것이며, 둘의 관계는 1:1이 아니다. 이것이 4장 §9(goroutine·virtual thread·async/await)에서 본 M:N 부활의 애플 판본이다.
| Mach thread (KT) | Swift Task | |
|---|---|---|
| 크기 | 스택 수백 KB~수 MB | 힙에 async frame 수백 바이트~ |
| 생성 주체 | 커널 workqueue(§8.4) | Swift 런타임(할당만) |
| 블로킹 시 | 그 KT가 통째로 멈춤 | await에서 suspend → KT를 반납 |
| 개수 한도 | 수천 개면 이미 위험(§8.4.2) | 수십만 개도 가능 |
핵심은 await의 의미다. Task가 await를 만나 대기 상태가 되면, 그 Task는 실행 중이던 커널 스레드를 붙잡은 채 잠들지 않는다 — 상태를 힙의 async frame에 저장하고 스레드를 양보한다. 그 KT는 즉시 다른 Task를 실행한다. 대기가 풀리면 Task는 아무 유휴 KT에서나 재개된다(재개된 스레드가 이전과 다를 수 있다).
await마다 KT를 양보하므로, 좁은 KT 집합으로 수십만 Task를 태울 수 있다.Swift Concurrency의 KT 풀은 폭이 코어 수로 고정돼 있다(GCD처럼 overcommit으로 늘리지 않는다). 그러므로 Task 안에서 Thread.sleep·동기 파일 I/O·세마포어 대기 같은 실제 블로킹을 하면 await가 아니라 KT 자체를 붙잡아 풀을 고갈시키고, 최악의 경우 전체가 멈춘다(deadlock 유발). 블로킹 작업은 await 가능한 async API로 바꾸거나, 정 필요하면 별도 전용 스레드/큐로 격리해야 한다.
이 부록은 스레드와 Task의 관계까지만 다룬다. actor가 어떻게 데이터 레이스(4장 §6)를 컴파일 타임에 막는지, Sendable·구조적 동시성·MainActor의 실전 적용은 별도 코스에서 이어진다.
8.83-way 비교 — Linux · Windows · macOS/iOS로 §8을 닫는다 ⊕ 확장
| 축 | Linux | Windows | macOS · iOS (XNU) |
|---|---|---|---|
| 커널 스레드 구조체 | task_struct(프로세스=스레드 통합) | KTHREAD + ETHREAD | Mach thread (+ BSD uthread) |
| 프로세스 컨테이너 | task_struct + 공유 mm/files | EPROCESS | BSD proc + Mach task |
| 사용자↔커널 매핑 | 1:1 (NPTL) | 1:1 | 1:1 (pthread ↔ Mach thread) |
| 생성 시스템 콜 | clone() | NtCreateThread | bsdthread_create / workqueue |
| 암시적 스레딩 런타임 | —(libⁿ 다양) | Thread Pool API | GCD(libdispatch) + 커널 workqueue |
| 대표 스케줄러 | CFS → EEVDF | 우선순위 기반 + 부스트 | Clutch(interactivity score) |
| 우선순위 상속 | PI-futex / rt_mutex | 우선순위 부스트 | Turnstile(multi-hop) |
| 경량 동시성(M:N) | —(goroutine은 Go 런타임) | — | Swift Task(협력적 스레드 풀) |
세 OS 모두 커널이 직접 스케줄하는 1:1 커널 스레드를 바닥에 깔고(§3.1의 결론), 그 위에 “작업 기반 병렬성”(§5)을 얹는다 — Windows는 Thread Pool, 애플은 GCD, 그리고 그보다 위에 goroutine/virtual thread/Swift Task 같은 M:N 경량 동시성(§9)이 “값싼 10만 동시성”을 준다. XNU가 다른 점은 이 세 층(1:1 커널 스레드 · GCD workqueue · Swift Task)이 커널 workqueue·Clutch·Turnstile로 촘촘히 통합돼 있다는 것이다.
이 부록에서 멈춘 곳들: Edge 스케줄러의 P-코어/E-코어 간 스레드 이동(migration) 세부 · kevent 워크큐의 자료구조 · os_signpost/Instruments의 System Trace로 turnstile 부스트와 스레드 생성 관찰하기 · libdispatch 오픈소스(src/queue.c·src/event/)의 실제 큐잉 경로. 참고: Apple libdispatch/XNU 오픈소스, WWDC “Modernizing Grand Central Dispatch Usage”·“Swift Concurrency: Behind the Scenes”, Levin *OS Internals.
8.9 용어 사전 ⊕ 확장
이 부록의 XNU·애플 특화 용어를 한자리에 모았다(스레드 일반 용어는 본문 §10.5 용어 사전 참조). 오른쪽 절 링크로 설명으로 이동한다.
① 커널 구조 (Mach · BSD)
| 용어 | 뜻 | 절 |
|---|---|---|
| XNU | macOS·iOS 커널. Mach 마이크로커널 + BSD 층의 하이브리드. | 8.3 |
| 마이크로커널 / 모놀리식 / 하이브리드 | 핵심만 커널에(나머지 사용자 서버) / 전부 커널에 / 둘의 절충. XNU는 하이브리드. | 8.3 |
| Mach task / thread | 자원 컨테이너(주소 공간·포트) / 실제 스케줄링 단위(thread_t). CPU엔 thread만 올라감. | 8.3 |
| BSD proc / uthread | PID·자격증명·fd 테이블 / 시스템 콜 처리 중의 BSD측 스레드 상태(errno 등). | 8.3 |
| Mach 포트(port) / IPC | 커널이 관리하는 단방향 메시지 큐 + 능력(capability) 기반 권한. Mach 통신의 통로. | 8.3 |
| SPI | System Programming Interface — 애플 내부/시스템 전용 비공개 인터페이스(공개 API의 반대). | 8.4 |
② 스레드 생성 (GCD ↔ 커널)
| 용어 | 뜻 | 절 |
|---|---|---|
| libdispatch | GCD의 사용자 공간 구현. 스레드를 직접 안 만들고 커널 workqueue에 요청만 한다. | 8.4 |
| (커널) workqueue | 실제 워커 스레드의 생성·캐싱·재사용을 시스템 부하 기반으로 결정하는 XNU 서브시스템. | 8.4 |
| overcommit | 워커가 블로킹으로 자리만 차지할 때, 코어 수를 넘겨 새 워커를 만드는 정책 → 스레드 폭발의 원인. | 8.4.2 |
| kqueue / kevent | 다수 소켓·타이머·파일 이벤트의 준비 상태를 한 번에 통지하는 커널 메커니즘. workqueue와 통합됨. | 8.4.3 |
③ 스케줄링 (Clutch · Turnstile)
| 용어 | 뜻 | 절 |
|---|---|---|
| Clutch 스케줄러 | 개별 스레드가 아니라 스레드 그룹을 단위로, interactivity score로 우선순위를 정하는 XNU 스케줄러. | 8.5 |
| interactivity score | 자발적 블로킹 시간 / CPU 사용 시간. 높으면 상호작용형(우선), 낮으면 배치형. | 8.5 |
| AMP / P코어·E코어 | 비대칭 멀티코어. 고성능·고전력 P코어와 저성능·저전력 E코어 혼합(big.LITTLE 계보). | 8.5.1 |
| Edge 스케줄러 | AMP의 코어 선택·이동까지 다루는 Clutch의 확장/후속. | 8.5.1 |
| 우선순위 역전 / 상속(Turnstile) | 저우선순위가 락을 쥐어 고우선순위를 막음 / 대기자의 우선순위를 보유자에게 전파해 해소. | 8.6 |
| base_pri / sched_pri | QoS가 번역된 기본 우선순위 / turnstile 부스트가 반영된 ‘실제’ 실행 우선순위. | 8.6 |
| multi-hop 전파 | 대기 사슬(대기자→sync 블록→큐 매니저→생성 스레드)을 따라 우선순위를 여러 단계로 상속. | 8.6.1 |
④ Swift Concurrency
| 용어 | 뜻 | 절 |
|---|---|---|
| Swift Task | 힙의 async frame(수백 바이트~)에 사는 경량 실행 단위. 커널 스레드와 1:1이 아님. | 8.7 |
| await / suspend | Task가 대기 시 상태를 힙에 저장하고 커널 스레드를 반납 — 붙잡은 채 잠들지 않음. | 8.7 |
| 협력적 스레드 풀 | 폭이 코어 수로 고정된 KT 풀(overcommit 안 함) → 블로킹하면 풀 고갈, 그래서 블로킹 금물. | 8.7 |