홈시리즈

© 2026 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

프로세스, 스레드, 메모리 — Node.js를 이해하기 위한 OS 기초 (2편)

정기창·2026년 2월 25일

1편에서 Node.js가 V8 엔진과 libuv로 구성된 런타임이라는 것을 확인했습니다. libuv는 내부적으로 스레드 풀이라는 것을 사용해서 파일 I/O 같은 작업을 처리한다고 했는데, 이 스레드 풀이 무엇인지 제대로 이해하려면 먼저 OS 수준에서 몇 가지 개념을 짚고 넘어가야 합니다.

프로세스가 뭔지, 스레드가 뭔지, 메모리는 어떻게 동작하는지. 이런 기초 없이 "libuv가 스레드 풀 4개로 파일을 읽는다"는 설명을 들어도 와닿지 않았습니다. 그래서 이번 글에서는 Node.js 코드에서 한 발짝 물러나, 컴퓨터가 프로그램을 어떻게 실행하는지부터 정리해보겠습니다.

프로그램은 어디에 있고, 어떻게 실행되는가

컴퓨터에서 프로그램이 실행되기까지의 흐름은 세 가지 하드웨어를 거칩니다.

구성 요소역할특징
SSD/HDD (저장소)프로그램 파일 보관전원 꺼져도 유지, 느림
RAM (메모리)실행 중인 프로그램 상주전원 끄면 사라짐, 빠름
CPU (프로세서)연산, 명령 처리가장 빠름

프로그램을 실행하면 이런 흐름이 됩니다.

SSD/HDD에 저장된 프로그램 파일
    ↓ 메모리로 로딩
RAM에 프로세스로 상주
    ↓ 명령어 실행
CPU가 연산 처리

VS Code를 더블클릭하면, SSD에 있는 VS Code 파일이 메모리에 올라가면서 프로세스가 됩니다. CPU는 그 프로세스의 명령어를 하나씩 읽어서 실행합니다. node app.js를 터미널에 입력하는 것도 마찬가지입니다. SSD에 설치된 Node.js 바이너리가 메모리에 올라가고, app.js 파일을 읽어서 V8 엔진이 실행합니다.

SSD와 HDD

SSD와 HDD 모두 전원이 꺼져도 데이터가 유지되는 저장소라는 점에서 역할은 같습니다. 차이는 속도와 물리적 구조에 있습니다.

항목HDDSSD
방식자기 디스크를 물리적으로 회전반도체 칩에 전기적 읽기/쓰기
속도느림빠름
내구성충격에 약함충격에 강함

SSD가 빠르면 프로그램 시작 속도가 빨라지고, Node.js에서 fs.readFile() 같은 파일 I/O 작업의 저장소 → 메모리 구간도 빨라집니다. 요즘 대부분의 컴퓨터가 SSD를 사용하는 이유이기도 합니다.

프로세스와 스레드

방금 "프로그램이 메모리에 올라가면 프로세스가 된다"고 했습니다. 이 프로세스 안에서 실제로 일을 하는 단위가 스레드입니다.

프로세스 — 실행 중인 프로그램

프로세스는 OS가 프로그램에 독립된 메모리 공간을 할당해서 만든 실행 단위입니다. Chrome을 켜면 Chrome 프로세스가 생기고, VS Code를 켜면 VS Code 프로세스가 생깁니다. 각 프로세스는 자기만의 메모리를 가지고 있어서, Chrome이 죽어도 VS Code에 영향을 주지 않습니다.

스레드 — 프로세스 안의 실행 단위

하나의 프로세스 안에서 여러 작업을 동시에 하고 싶으면 스레드를 만듭니다. 같은 프로세스의 스레드들은 메모리를 공유하면서 각자 다른 일을 할 수 있습니다.

프로세스 (Chrome)
├─ 스레드 1: 탭 1 렌더링
├─ 스레드 2: 탭 2 렌더링
├─ 스레드 3: 네트워크 요청
└─ 스레드 4: 파일 다운로드
     ↑ 같은 메모리를 공유하면서 동시에 일함
구분프로세스스레드
메모리독립 (각자 별도)공유 (같은 프로세스 내)
생성 비용큼작음
하나가 죽으면다른 프로세스에 영향 없음같은 프로세스의 다른 스레드에 영향
예시Chrome, VS Code 각각Chrome 안의 탭, 네트워크 처리

Node.js에서 "싱글 스레드"라고 할 때, 이 스레드가 바로 여기서 말하는 스레드입니다. JavaScript 코드를 실행하는 스레드가 하나라는 뜻입니다. 하지만 libuv의 스레드 풀은 별도의 스레드를 가지고 있으므로, Node.js 프로세스 전체가 스레드 하나로만 돌아가는 것은 아닙니다.

CPU 코어와 스레드의 관계

프로그램에서 스레드를 여러 개 만든다고 했는데, 그 스레드들은 누가 실행하는 걸까요? CPU 코어입니다.

하드웨어 스레드 vs 소프트웨어 스레드

"스레드"라는 단어가 두 가지 맥락에서 쓰이기 때문에 혼란이 생길 수 있습니다.

구분의미예시
하드웨어 스레드CPU 코어가 동시에 처리할 수 있는 수M2 Pro: 12코어 12스레드
소프트웨어 스레드코드에서 만드는 실행 단위수백~수천 개 가능

Intel CPU에서 "8코어 16스레드"라는 스펙을 본 적 있을 것입니다. 이건 하이퍼스레딩 기술로 코어 1개가 스레드 2개를 처리하는 것입니다. Apple Silicon(M1, M2 등)은 하이퍼스레딩이 없어서 코어 수와 하드웨어 스레드 수가 같습니다.

CPU 코어가 12개인데 소프트웨어 스레드가 100개면 어떻게 될까요? OS가 컨텍스트 스위칭이라는 방법으로 스레드들을 빠르게 번갈아 실행합니다. 한 번에 12개만 진짜 동시에 실행되지만, 나머지 스레드도 아주 빠르게 교대하기 때문에 사람 눈에는 모두 동시에 돌아가는 것처럼 보입니다.

스레드 풀이 뭔가

"풀(Pool)"이라는 단어가 붙으면 미리 만들어놓고 재사용한다는 의미입니다. 커넥션 풀, 스레드 풀 모두 같은 패턴입니다.

풀 없이: 요청 → 스레드 생성 → 작업 → 스레드 파괴 (매번 비용 발생)
풀 방식: Node.js 시작 → 스레드 4개 미리 생성 → 작업 할당 → 반납 → 재사용

libuv는 Node.js가 시작될 때 기본 4개의 워커 스레드를 미리 만들어둡니다. 파일 I/O 같은 작업이 들어오면 빈 워커에 할당하고, 작업이 끝나면 워커를 다시 풀로 반납합니다. UV_THREADPOOL_SIZE 환경변수로 최대 1024개까지 늘릴 수 있습니다.

4개 워커의 의미

워커가 4개라는 것은 동시에 4개의 작업을 병렬로 처리할 수 있다는 뜻입니다.

fs.readFile('a.txt')   → 워커 1이 처리
fs.readFile('b.txt')   → 워커 2가 처리
fs.readFile('c.txt')   → 워커 3이 처리
fs.readFile('d.txt')   → 워커 4가 처리
fs.readFile('e.txt')   → 대기열에서 워커가 비길 기다림

5번째 파일 읽기 요청이 오면 4개 워커가 모두 바쁘기 때문에 큐에서 대기합니다. 기존 워커 중 하나가 작업을 마치고 반납되면 그때 5번째 작업이 시작됩니다.

작업 큐는 어떻게 동작하는가

여기서 "큐에서 대기한다"는 말이 좀 더 궁금해질 수 있습니다. 이 큐는 누가 관리하는 걸까요?

작업 큐는 스레드가 아닙니다. 프로세스 메모리에 올라가 있는 데이터 구조(자료구조)일 뿐입니다. "할 일 목록"이 적혀있는 메모장이라고 생각하면 됩니다. 큐 자체가 무언가를 실행하지 않습니다.

Node.js 프로세스 (메모리)
├─ 메인 스레드 (V8, JS 실행)        ← 실행 단위
├─ 워커 스레드 1~4                   ← 실행 단위
└─ 작업 큐 (wq)                     ← 데이터 (할 일 목록)

큐에 넣고 빼는 역할도 나뉘어 있습니다.

  • 큐에 넣는 것 → 메인 스레드가 합니다. fs.readFile()을 호출하면 메인 스레드가 작업을 큐에 추가합니다.
  • 큐에서 꺼내는 것 → 워커 스레드가 합니다. 빈 워커가 큐에서 작업을 가져갑니다.
메인 스레드: fs.readFile() 호출 → 큐에 작업 추가
                                     ↓
워커 스레드: 큐에서 작업 꺼냄 → 실행 → 완료 → 다시 큐 확인

이 구조를 프로듀서-컨슈머(Producer-Consumer) 패턴이라고 부릅니다. 메인 스레드가 일감을 만들어서 큐에 넣고(produce), 워커 스레드가 큐에서 꺼내서 처리(consume)합니다. 사실 1~4번째 작업도 큐를 거칩니다. 다만 빈 워커가 있으니 즉시 꺼내가는 것뿐이고, 5번째처럼 빈 워커가 없을 때만 큐에 실제로 대기하게 됩니다.

할 일 없는 워커는?

할 일이 없는 워커 스레드는 sleep 상태에 들어갑니다. CPU를 점유하지 않습니다. OS가 "이 스레드는 지금 할 일 없음"이라고 판단하면 CPU 코어 스케줄링에서 제외시킵니다. 그래서 Node.js가 켜져있다고 해서 libuv 스레드 풀이 CPU 코어 4개를 영구적으로 잡고 있는 건 아닙니다.

CPU 코어스레드 풀 4개실제 동작
1코어4개 스레드번갈아 실행 (동시처럼 보임)
4코어4개 스레드진짜 동시 실행
12코어 (M2 Pro)4개 스레드진짜 동시 실행, 나머지 코어는 V8·OS 등

프로세스의 메모리 관리

프로세스가 시작될 때 필요한 메모리를 한꺼번에 다 받는 것은 아닙니다. 필요할 때마다 OS에 요청해서 동적으로 할당받습니다.

Node.js에서는 V8 엔진이 메모리를 관리합니다. V8의 힙(Heap) 메모리 기본 한계는 약 1.5GB(64bit 기준)이고, --max-old-space-size 옵션으로 조절할 수 있습니다. 이 한계를 넘으면 JavaScript heap out of memory 에러가 발생하면서 프로세스가 종료됩니다.

# V8 힙 메모리를 4GB로 늘리기
node --max-old-space-size=4096 app.js

메모리가 부족해지면 V8의 가비지 컬렉터(GC)가 더 이상 사용하지 않는 객체를 찾아서 메모리를 해제합니다. 이 과정에 대해서는 이 시리즈 후반부에서 다루겠습니다.

이것들이 Node.js와 어떻게 연결되는가

이번 글에서 다룬 개념들을 Node.js 관점으로 정리하면 이렇게 됩니다.

OS 개념Node.js에서의 의미
프로세스node app.js를 실행하면 생기는 Node.js 프로세스
메인 스레드V8이 JavaScript를 실행하는 싱글 스레드
스레드 풀libuv가 파일 I/O 등을 처리하는 워커 스레드 (기본 4개)
작업 큐워커가 바쁠 때 작업이 대기하는 메모리 상의 자료구조
메모리V8 힙 (~1.5GB 기본), 동적 할당
CPU 코어메인 스레드 + 워커 스레드들이 OS 스케줄링에 의해 배치됨

Node.js가 "싱글 스레드"라는 말은 JavaScript 실행 스레드가 하나라는 뜻이지, Node.js 프로세스 전체가 하나의 스레드로 동작한다는 뜻이 아닙니다. libuv 스레드 풀, V8 GC 스레드 등 내부적으로는 여러 스레드가 함께 일하고 있습니다.

다음 글(3편)에서는 이 싱글 스레드 위에서 어떻게 여러 작업을 동시에 처리하는 것처럼 보이는지, 콜 스택과 이벤트 루프를 통해 살펴보겠습니다.

Node.js프로세스스레드OSCPU메모리스레드 풀면접 준비

관련 글

Node.js는 싱글 스레드인데 어떻게 동시에 처리할까 — 콜 스택과 이벤트 루프 (3편)

1편에서 Node.js의 내부 구조를, 2편에서 프로세스와 스레드의 기본 개념을 확인했습니다. 이번에는 "싱글 스레드인데 어떻게 동시 처리가 가능한가"라는 질문에 답하기 위해, 콜 스택과 이벤트 루프의 관계, libuv가 작업을 처리하는 두 가지 방식, 그리고 이벤트 루프 6개 페이즈의 실체를 소스코드로 확인해봤습니다.

관련도 93%

Node.js 소스코드를 직접 열어봤습니다 — 런타임, V8, libuv의 실체 (1편)

면접 준비를 하다가 "Node.js가 뭔가요?"라는 질문에 제대로 답할 수 없다는 걸 깨달았습니다. 런타임이 뭔지, V8과 libuv가 각각 무슨 역할인지, 실제 Node.js GitHub 소스코드를 열어서 os.hostname() 한 줄이 OS까지 도달하는 과정을 추적해봤습니다.

관련도 92%

PHP 동작원리와 Node.js 비교 — 요청은 어떻게 처리되는가

PHP-FPM의 멀티프로세스 모델과 Node.js의 싱글스레드 이벤트루프는 요청을 완전히 다른 방식으로 처리합니다. 5년간 PHP로 결제 시스템을 개발하고 Node.js로 전환한 경험을 바탕으로, 두 런타임의 동작원리와 구조적 차이를 정리했습니다.

관련도 91%