JUNSEOK
03 · 운영체제·20분·4개 레슨

프로세스와 스레드

Process vs Thread, 컨텍스트 스위칭, JS 싱글 스레드.

목표: 프로세스와 스레드의 차이, PCB, 컨텍스트 스위칭, IPC 를 이해하고, 크롬의 멀티프로세스 아키텍처와 JS의 싱글 스레드 선택 이유를 OS 수준에서 설명할 수 있어야 한다.


0. "프로그램이 실행된다"는 게 뭔가 — 초심자용

0-1. 프로그램 vs 프로세스

  • 프로그램(Program): 하드디스크에 저장된 파일 (.exe, .app). 가만히 있는 레시피.
  • 프로세스(Process): 그 프로그램을 실행시킨 상태. OS가 메모리를 할당해주고, CPU 시간을 배정해준 활동 중인 요리사.

크롬 아이콘을 더블클릭하는 순간, OS는 디스크의 chrome.exe를 읽어 메모리에 복사하고, CPU 일부를 배정해 "움직이기 시작"하게 한다. 이게 프로세스.

0-2. 스레드는 또 뭐고

한 프로세스 안에서 여러 일을 동시에 처리하고 싶다. 예: 크롬이 페이지를 그리면서, 다운로드도 하고, 탭 간 전환도 받아야 한다. 이를 위해 프로세스 안에 실행 흐름을 여러 개 둔다 — 이게 스레드(Thread).

비유: 프로세스 = 식당, 스레드 = 식당 안의 직원. 같은 주방·같은 식재료(메모리) 를 쓰지만, 각자 다른 주문을 처리한다.

0-3. 프론트엔드 개발자와의 접점

왜 프론트가 OS 개념을 알아야 하나?

  • Chrome의 탭마다 프로세스가 다르다 → 한 탭이 죽어도 다른 탭은 살아남는다 (사이트 격리, Site Isolation)
  • JS는 "싱글 스레드"다 → 이 말의 진짜 의미는 이 장에서 드러남 (2-6의 토대)
  • Web Worker → 브라우저에서 멀티 스레드 흉내 내기
  • Node.js는 단일 프로세스 + 이벤트 루프 → 성능 특성이 전부 여기서 나옴

0-4. 이 장의 핵심 용어

용어한 줄 의미
프로세스실행 중인 프로그램 단위. 독립된 메모리 공간.
스레드프로세스 안의 실행 흐름. 메모리를 공유.
PCBOS가 각 프로세스를 관리하려고 만든 "주민등록증".
컨텍스트 스위칭CPU 하나가 여러 프로세스/스레드를 번갈아 처리하는 것.
IPC프로세스끼리 대화하는 방법 (메모리가 분리돼 있으니).

0-5. "동시성"이라는 착각

CPU 코어가 1개여도 "동시에 여러 일이 돌아가는 것처럼" 보인다. 실제로는 아주 빠르게 번갈아 가며 처리하는 것. 인간 눈에는 동시로 보인다. 이 "번갈아 가기"의 비용이 컨텍스트 스위칭 — 공짜가 아니다. 이 장에서 그 비용을 실감하게 된다.


1. 프로세스 (Process)

실행 중인 프로그램의 인스턴스. OS가 자원을 할당하는 최소 단위.

구성

┌──────────────────────────┐
Text (Code)         │ ← 실행할 명령어
├──────────────────────────┤
Data (Global)       │ ← 초기화된 전역 변수
├──────────────────────────┤
BSS                 │ ← 미초기화 전역 변수
├──────────────────────────┤
│      Heap         ↓↓↓    │ ← malloc, new
│        (위로 증가)        │
│                          │
│        (아래로 증가)      │
│      Stack       ↑↑↑     │ ← 함수 호출 프레임
└──────────────────────────┘

자원 소유

  • PID (Process ID)
  • 메모리 공간 (독립적)
  • 파일 디스크립터 (열린 파일·소켓)
  • 스레드 (최소 1개, 메인 스레드)
  • 신호 처리기
  • 환경 변수

2. 스레드 (Thread)

프로세스 안의 실행 흐름. 같은 프로세스 내 스레드는 메모리를 공유한다.

프로세스 vs 스레드

프로세스스레드
메모리독립공유 (Text/Data/Heap)
생성 비용크다작다
통신IPC 필요 (비싸다)공유 메모리 (싸다)
안정성하나 죽어도 다른 것 영향 없음하나 죽으면 전체 프로세스 크래시
컨텍스트 스위칭비쌈 (TLB flush)싸다

스레드가 자체적으로 갖는 것

  • 스택 (함수 호출 프레임)
  • 레지스터 (PC, SP 등)
  • 스레드 로컬 스토리지 (TLS)

3. PCB (Process Control Block)

OS 커널이 프로세스별로 관리하는 구조체.

struct task_struct {  // Linux
  pid_t pid;
  int state;              // RUNNING, READY, BLOCKED, ZOMBIE...
  unsigned long priority;
  struct mm_struct *mm;   // 메모리 맵
  struct files_struct *files; // FD 테이블
  struct signal_struct *signal;
  struct task_struct *parent;
  struct list_head children;
  struct thread_info thread; // 레지스터, 스택 포인터
  // ...수백 필드
};

4. 프로세스 상태 (State Diagram)

        ┌─────────┐
        │   New   │
        └────┬────┘

        ┌─────────┐    scheduler    ┌─────────┐
 ┌─────▶│  Ready  │────────────────▶│ Running │
 │      └─────────┘   preemption    └────┬────┘
 │            ▲       ◀────────────────  │
 │            │                          │ I/O wait
 │            │ I/O done                 ▼
 │      ┌─────┴────┐                ┌─────────┐
 │      │ Blocked  │◀───────────────│ Waiting │
 │      └──────────┘                └─────────┘
 │                                       │
 │                                       ▼
 │                                  ┌─────────┐
 └──────────────────────────────────│  Exit   │
                                    └─────────┘

5. Context Switching

OS가 한 프로세스/스레드의 실행을 멈추고 다른 것으로 교체하는 과정.

단계

  1. 현재 실행 중인 프로세스의 레지스터·PC·SP 를 PCB에 저장
  2. 다음 실행할 프로세스의 PCB에서 레지스터 복원
  3. TLB flush (프로세스 간 전환 시) — 페이지 테이블이 바뀌므로
  4. 캐시 오염

비용

  • 수 마이크로초~수십 마이크로초
  • 빈번하면 전체 처리량 저하
  • 스레드 간 전환은 TLB flush 불필요 → 더 쌈

트레이드오프

짧은 타임 슬라이스긴 타임 슬라이스
반응성 ↑처리량 ↑
컨텍스트 스위칭 오버헤드 ↑공정성 ↓

6. 스케줄링

주요 알고리즘

알고리즘특징
FCFS먼저 온 순서. 짧은 작업이 긴 작업 뒤에 갇힘(convoy effect)
SJF짧은 작업 우선. 기아 가능
Round Robin타임 슬라이스 순환. 반응성 좋음
Priority우선순위 기반. 기아 방지 위해 aging
Multilevel Feedback Queue실제 OS 대부분. 우선순위 자동 조정
CFS (Linux)vruntime 기반 공정 분배. Red-Black Tree

선점 vs 비선점

  • 선점(Preemptive): OS가 강제로 빼앗음 (타이머 인터럽트)
  • 비선점(Cooperative): 프로세스가 자발적으로 양보 (yield)

7. IPC (Inter-Process Communication)

프로세스는 메모리가 격리돼 있어 전용 통신 수단이 필요.

수단

방법특징
Pipe부모-자식 단방향. 쉘의 `
Named Pipe (FIFO)이름 있는 파이프, 임의 프로세스 간
Message Queue커널이 관리하는 큐
Shared Memory가장 빠름. 동기화 필요 (세마포어, 뮤텍스)
Socket네트워크 (또는 Unix Domain Socket)
Signal이벤트 알림 (SIGKILL, SIGTERM, SIGCHLD)
Memory-mapped File파일을 메모리로 매핑, 프로세스 간 공유 가능

FE 연관

  • 크롬 브라우저의 Renderer ↔ Browser 프로세스: Mojo IPC (shared memory + socket)
  • Node.js child_process.fork(): IPC 채널로 .send() / message 이벤트

8. 크롬의 멀티프로세스 아키텍처

┌─────────────────────────────────────────────┐
│          Browser Process (1개)               │
- UI, 네트워크, 저장소, 주소창               │
└─────────────────────────────────────────────┘
           ▲          ▲           ▲
     IPCIPCIPC
           ▼          ▼           ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Renderer 1   │ │ Renderer 2   │ │ Renderer 3
│ (sandbox)    │ │ (sandbox)    │ │ (sandbox)    │
- HTML 파싱   │ │              │ │              │
- JS 실행     │ │              │ │              │
- 렌더링       │ │              │ │              │
└──────────────┘ └──────────────┘ └──────────────┘

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
GPU Process  │ │ Utility      │ │ Plugin       │
└──────────────┘ └──────────────┘ └──────────────┘

왜 멀티프로세스인가

  1. 안정성: 탭 하나 크래시해도 브라우저 전체가 죽지 않음
  2. 보안: 각 Renderer는 샌드박스 → 취약점이 있어도 파일시스템 접근 불가
  3. 성능: 멀티코어 활용, 탭 간 GC 영향 격리

Site Isolation

  • Spectre/Meltdown 대응으로 각 Origin별로 별도 Renderer
  • 메모리 사용↑, 보안 격리↑

9. 왜 JavaScript는 싱글 스레드인가

역사적 이유

  • 1995년 Brendan Eich가 10일 만에 설계
  • DOM은 공유 자원 — 멀티 스레드면 락·경쟁 조건 폭발

대안

방법
CPU 집약 작업Web Worker
격리된 실행iframe + postMessage
병렬 요청Promise.all (I/O는 비동기)
실제 병렬 계산SharedArrayBuffer + Atomics + Worker

Web Worker가 DOM에 접근하지 못하는 이유

DOM은 스레드 안전하지 않다. 여러 스레드가 동시에 appendChild 하면 트리가 깨짐. JS 생태계는 처음부터 싱글 스레드 DOM을 전제로 진화했기에 이제는 바꿀 수 없다.


10. Node.js의 모델

┌──────────────────────────┐
│   Main Thread (V8 + 이벤트 루프)    │
└──────────────────────────┘


┌──────────────────────────┐
│      libuv Thread Pool   │
│   (기본 4개, 최대 1024)    │
- 파일 I/O
- DNS
- crypto (pbkdf2)      │
- 압축 (zlib)           │
└──────────────────────────┘
  • 네트워크 I/O는 OS의 async I/O (epoll/kqueue/IOCP) 사용 → 스레드 풀 안 씀
  • CPU 작업은 worker_threads 모듈로 별도 스레드

11. 스레드 동기화 (이론)

공유 자원에 여러 스레드가 접근하면 경쟁 조건 발생.

원시 자료구조

설명
Mutex한 번에 한 스레드만 소유
SemaphoreN개의 권한, P(wait) / V(signal)
Condition Variable조건 충족까지 대기, signal로 깨움
Read-Write Lock읽기는 동시, 쓰기는 독점
Spinlock락 획득까지 busy-wait. 짧은 임계 영역용
Atomic락 없이 단일 연산 보장 (CAS, FAA)

고전 문제

  • Deadlock: 서로 상대가 가진 자원을 기다림 (4조건: 상호 배제, 점유 대기, 비선점, 순환 대기)
  • Starvation: 낮은 우선순위가 영원히 실행 안 됨
  • Priority Inversion: 높은 우선순위가 낮은 것을 기다림 (Mars Pathfinder 사례)

JS에서의 의의

JS 자체는 싱글 스레드라 이런 문제에서 대부분 자유. 단 SharedArrayBuffer 를 쓸 때 Atomics.wait/notify 로 직접 동기화 필요.

// Worker 간 공유 메모리
const sab = new SharedArrayBuffer(1024);
const view = new Int32Array(sab);
Atomics.store(view, 0, 42);
Atomics.wait(view, 0, 42); // 값이 42면 대기
Atomics.notify(view, 0, 1); // 1개 깨움

12. ⚠️ 자주 하는 실수·오해

오해실제
스레드가 많을수록 빠르다CPU 코어보다 많으면 오히려 컨텍스트 스위칭으로 느려짐
프로세스 생성은 공짜다fork는 비싸다. 스레드 풀이 기본
크롬 탭 하나 = 프로세스 하나Site Isolation + 통합 규칙으로 달라짐
Worker로 감싸면 모든 게 빨라진다postMessage 복사 비용, CPU 바운드만 유리
Node.js는 싱글 스레드다JS 실행만 싱글, 내부엔 libuv 스레드 풀

13. 연습 문제

Q1. 프로세스와 스레드의 가장 본질적인 차이는?

정답

메모리 공간. 프로세스는 독립적 가상 메모리를 갖고, 같은 프로세스의 스레드는 Text/Data/Heap을 공유한다(스택만 분리). 그래서 스레드 간 통신은 공유 메모리로 싼 반면, 프로세스 간은 IPC로 비싸다.

Q2. 크롬이 탭마다 프로세스를 분리하는 3가지 이유는?

정답
  1. 안정성 — 한 탭 크래시가 전체를 죽이지 않음
  2. 보안 — 샌드박스로 OS 자원 접근 차단, Spectre 대응 Site Isolation
  3. 성능 — 멀티코어 활용, 탭별 V8 힙 분리로 GC 영향 격리

Q3. 컨텍스트 스위칭이 비싼 이유 2가지는?

정답
  1. 레지스터·PC 저장·복원 + TLB flush + 캐시 오염 (하드웨어 비용)
  2. 캐시 웜업 비용 — 새 프로세스의 데이터는 L1/L2/L3에 없어 캐시 미스가 집중적으로 발생

스레드 간 전환은 TLB flush가 없어 조금 저렴.

Q4. Node.js가 싱글 스레드라면서 파일 I/O는 어떻게 병렬로 하는가?

정답

JS 실행은 싱글 스레드지만, 내부적으로 libuv가 스레드 풀(기본 4개) 을 운영한다. 파일 I/O, DNS, crypto, zlib 같은 작업은 이 풀에서 병렬 실행되고 완료되면 콜백이 메인 스레드의 이벤트 루프에 등록된다. 네트워크 I/O는 OS의 async I/O(epoll/kqueue/IOCP)를 사용해 스레드 풀 없이 처리한다.

Q5. Web Worker가 DOM을 건드리지 못하는 이유는?

정답

DOM API는 스레드 안전하지 않기 때문. 여러 스레드가 동시에 트리를 수정하면 락 없이는 일관성을 유지할 수 없고, DOM에 락을 붙이면 성능이 심각하게 저하된다. 따라서 Worker는 메시지 전달(postMessage)로만 메인 스레드와 통신한다.

Q6. Deadlock의 4가지 필요조건은?

정답
  1. 상호 배제 (Mutual Exclusion): 자원을 한 번에 하나만 소유
  2. 점유 대기 (Hold and Wait): 자원을 갖고 다른 자원을 기다림
  3. 비선점 (No Preemption): 자원을 강제로 뺏을 수 없음
  4. 순환 대기 (Circular Wait): 프로세스들이 원형으로 자원을 기다림

하나라도 깨면 Deadlock 불가능. 보통 순환 대기를 자원 획득 순서로 깬다.

Q7. SharedArrayBuffer 는 왜 기본 비활성화 상태였다가 COOP/COEP 헤더를 요구하게 됐는가?

정답

Spectre 사이드 채널 공격 때문. SharedArrayBuffer 로 정밀 타이머를 만들 수 있어 CPU 추측 실행 취약점으로 메모리를 엿볼 수 있었다. 2018년 일시 비활성화됐다가, Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corpOrigin을 격리된 에이전트 클러스터로 분리 해야만 다시 활성화된다. Cross-origin 리소스가 격리되므로 공격 표면이 줄어든다.


14. 체크리스트

  • 프로세스와 스레드의 차이(메모리, 비용, 통신)를 설명할 수 있다
  • PCB에 저장되는 정보를 안다
  • 컨텍스트 스위칭의 단계와 비용을 이해한다
  • IPC 수단을 상황에 따라 선택할 수 있다
  • 크롬 멀티프로세스 아키텍처의 이유를 안다
  • JS가 싱글 스레드인 이유를 역사·기술 양쪽으로 설명할 수 있다
  • Node.js가 libuv 스레드 풀을 쓰는 지점을 안다
  • Deadlock 4조건을 암기하고 있다

← 2-9. 캐싱과 CDN | 3-2. 동기·비동기와 I/O 모델 →

진도 체크시작 전
NEXT · 3-2

동기·비동기와 I/O 모델

Blocking vs Non-blocking, 이벤트 기반 I/O.

이어서 학습하기 →