담비의 개발블로그

[CS]Node.js는 싱글스레드인데 비동기 처리 하는 원리 본문

CS(컴퓨터과학)

[CS]Node.js는 싱글스레드인데 비동기 처리 하는 원리

담비12 2025. 7. 6. 15:09

 

멀티스레드: 여러 작업을 동시에 실행할 수 있음 (여러 CPU 코어 사용)

싱글스레드: 한 번에 하나의 작업만 실행 가능

 

Node.js는 JavaScript 런타임 환경으로, 이벤트 기반이고 싱글 스레드로 동작한다.

그러나 "싱글 스레드라서 느릴 것 같다"는 예상과 달리 I/O 작업에 매우 효율적이다.

핵심 구조는 이벤트 루프(Event Loop) 와 비동기 I/O 처리이다.

 

 

I/O 작업이란?

- I/O는 Input / Output의 줄임말

- 컴퓨터에서 I/O는 보통 프로그램이 외부 자원과 데이터를 주고받는 작업을 말한다.

 

 

▶ Node.js 의 비동식 처리 방식

 

1. 싱글 스레드 메인 루프 (Event Loop)

▷ 개념

Event Loop는 Node.js의 중심 구조이다.

콜백 큐를 계속해서 확인하면서 작업이 완료된 콜백을 하나씩 실행한다.

          [작업 요청]
                ↓
[이벤트 루프] → [콜백 큐] → [콜백 실행]

 

특징

메인 루프는 계속 돌아가면서 "할 일이 있는지" 살펴본다.

하나의 콜백만 순서대로 실행한다. (싱글 스레드라서 동시에 실행은 안 됨)

 

2. 백그라운드 쓰레드 풀 (libuv - Thread Pool)

 

libuv란?

Node.js가 내부적으로 사용하는 C/C++ 기반의 라이브러리이다. (Node.js 는 C/C++로 만들고 사용하는 방법은 JavaScript기반)

비동기 I/O 처리를 가능하게 해주는 핵심 기술이다.

4개 정도의 백그라운드 스레드 풀을 만들어서 I/O 같은 느린 작업을 대신 처리한다.

작업유형 백그라운드에서 처리
fs.readFile() 파일을 백그라운드에서 읽음
dns.lookup() DNS 조회
crypto.pbkdf2() 암호화 연산
기타 오래 걸리는 작업

 

 

3. 비동기 콜백/프로미스 등록

 

▷ 비동기 콜백

fs.readFile('file.txt', (err, data) => {
  console.log(data); // 나중에 실행됨
});

readFile() 호출 시, 내부적으로 libuv에 작업을 등록한다.

작업은 백그라운드 스레드에서 처리되고, 완료되면 콜백 함수를 콜백 큐(queue)에 넣는다.

 

◇ 콜백 큐란?

콜백 큐(callback queue)는 나중에 실행될 작업(함수)을 저장하는 줄(queue)이다.

 

콜백 큐 위치

콜백 큐는 Node.js나 브라우저가 내부적으로 가지고 있는 메모리 영역에 존재하는 구조이다.

이건 자바스크립트 엔진(V8)이나 이벤트 루프(libuv)가 자체적으로 관리한다.

우리 눈에 보이진 않지만, 비동기 작업 완료 시마다 거기에 함수들이 차곡차곡 들어간다.

┌────────────┐
│  JS 코드   │ ← 싱글 스레드로 실행됨
└────┬───────┘
     ↓
┌──────────────┐
│ 비동기 요청   │ (fs, http, timer 등)
└────┬─────────┘
     ↓
┌──────────────┐
│ libuv 백그라운드 │ ← 실제 작업 처리 (스레드 풀)
└────┬─────────┘
     ↓
┌──────────────┐
│  ✅ 콜백 큐    │ ← 콜백 함수가 여기 들어옴!
└────┬─────────┘
     ↓
┌──────────────┐
│  이벤트 루프   │ ← 콜백을 꺼내 실행
└──────────────┘

 

 

프로미스 작동방식

fetch('https://example.com')
  .then(res => res.json())
  .then(data => console.log(data));

Promise도 내부적으로 마이크로태스크 큐(Microtask Queue)에 들어가고, 이벤트 루프가 "현재 작업이 끝난 뒤" 실행한다.

 

 

 

4. 작업 완료 후 콜백 큐 처리

작업이 완료되면 콜백이 큐에 등록되고, 이벤트 루프가 그것을 실행한다.

 

[파일 읽기 완료!] → 콜백 큐 등록 → 이벤트 루프가 감지 → 실행

 

 

전체 흐름

1. 자바스크립트 코드 실행

2. 비동기 함수 호출 (예: readFile)

3. libuv가 백그라운드 쓰레드로 전달

4. 자바스크립트는 다음 코드로 넘어감 (안 멈춤!)

5. 작업 완료 → 콜백 큐에 등록

6. 이벤트 루프가 콜백 큐 확인

7. 콜백 실행 (결과 출력 등)

 

개념 설명
I/O 작업 외부 자원과 데이터를 주고받는 작업 (파일, 네트워크 등)
싱글 스레드 자바스크립트 코드 실행은 1개의 스레드만 사용
이벤트 루프 콜백 큐를 계속 돌며 하나씩 작업을 실행
libuv 쓰레드 풀 시간이 오래 걸리는 I/O 작업은 백그라운드에서 비동기로 처리
콜백/프로미스 결과가 준비되면 큐에 등록되어 나중에 실행됨

 


 

1. 이벤트 루프는 무엇인가?

이벤트 루프(Event Loop)는 Node.js의 심장이다.

메인 스레드에서 계속해서 작업이 있는지 감시하고, 처리할 콜백 함수가 있다면 실행한다.

쉽게 말해, JavaScript의 "main thread"가 큐에 있는 작업을 하나씩 처리하는 구조이다.

 

2. 블로킹과 논블로킹의 차이

블로킹 I/O: 파일을 읽을 때까지 코드가 멈춤.

논블로킹 I/O: 파일 읽기를 요청만 하고, 바로 다음 코드 실행. 나중에 읽기 완료되면 알려줌.

Node.js는 대부분의 API가 비동기(논블로킹) 방식으로 설계되어 있음.

 

3. 비동기 작업의 흐름

 

예: 파일을 비동기로 읽기

const fs = require('fs');

console.log('Start');

fs.readFile('data.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log('File content:', data);
});

console.log('End');

 

실행 순서

1. 'Start' 출력
2. fs.readFile() 호출 → 백그라운드로 넘김
3. 'End' 출력
4. 파일 읽기 완료되면 콜백 큐에 등록됨
5. 이벤트 루프가 그 콜백을 실행 → 'File content: ...' 출력

 

4. libuv와 스레드풀의 역할

Node.js는 자체적으로는 싱글 스레드이지만, libuv라는 C 라이브러리가 내부에서 스레드 풀(thread pool)을 사용한다.

 

메인 스레드가 하지 않는 일

- 파일 시스템 접근

- DNS 조회

- 압축/암호화 등 CPU 작업

이런 작업은 백그라운드 스레드에서 실행되고, 완료되면 콜백이 이벤트 루프 큐로 돌아간다.