블로킹

어떤 작업이 완료될 때까지 호출한 쪽의 실행 흐름이 멈추고 대기하는 상태를 뜻합니다.

비유

[ 블로킹 — 식당 대기 ]
주문 → 카운터 앞에서 음식 나올 때까지 서서 기다림
       ↑ 그동안 아무것도 못 함

[ 논블로킹 — 진동벨 ]
주문 → 진동벨 받음 → 자리에서 다른 일 → 벨 울리면 음식 수령
       ↑ 기다리는 동안 다른 일 가능

코드로 보는 차이

블로킹

// 블로킹 — readFileSync는 파일을 다 읽을 때까지 멈춤
const data = fs.readFileSync("big-file.txt");   // 3초간 멈춤
console.log("파일 읽기 완료");                    // 3초 후 실행
console.log("다음 작업");                         // 그 다음 실행
시간 ──────────────────────────────────────→

readFileSync  ████████████████████ (3초 블로킹)
"파일 읽기 완료"                    ██
"다음 작업"                           ██

논블로킹

// 논블로킹 — readFile은 즉시 반환, 완료 시 콜백 호출
fs.readFile("big-file.txt", (err, data) => {
    console.log("파일 읽기 완료");      // 3초 후 실행
});
console.log("다음 작업");               // 즉시 실행
시간 ──────────────────────────────────────→

readFile 호출  █ (즉시 반환)
"다음 작업"      ██ (바로 실행)
파일 I/O             ████████████████████ (백그라운드)
"파일 읽기 완료"                          ██ (콜백 실행)

블로킹이 발생하는 대표적인 상황

1. I/O 작업

파일 읽기/쓰기       디스크 접근 대기
네트워크 요청        서버 응답 대기
DB 쿼리             쿼리 결과 대기
사용자 입력          키보드·마우스 입력 대기

2. 동기 함수 호출

// Java — 블로킹 I/O
InputStream in = socket.getInputStream();
int data = in.read();    // 데이터가 올 때까지 스레드 멈춤

3. Lock 대기

synchronized (lock) {
    // 다른 스레드가 lock을 쥐고 있으면
    // 이 스레드는 여기서 블로킹
}

4. sleep

Thread.sleep(3000);   // 3초간 스레드 블로킹

블로킹이 문제가 되는 이유

단일 스레드에서 블로킹

[ 브라우저 메인 스레드 ]

사용자 클릭 → 서버 요청 (블로킹 3초) → 응답 처리
                    ↑
              이 동안 화면 멈춤
              버튼 클릭 안 됨
              스크롤 안 됨
              애니메이션 정지
              → "페이지가 응답하지 않습니다"

서버에서 블로킹

[ 스레드 1개당 요청 1개 처리 — 전통적 블로킹 서버 ]

스레드 1: 요청 A 처리 중 ── DB 쿼리 (블로킹 500ms) ── 응답
스레드 2: 요청 B 처리 중 ── 파일 읽기 (블로킹 200ms) ── 응답
스레드 3: 요청 C 처리 중 ── API 호출 (블로킹 1초) ── 응답
...
스레드 500: 요청 처리 중

501번째 요청 → 스레드 없음 → 대기 또는 거부
               ↑ 스레드가 블로킹 상태로 놀고 있는데 새 요청 못 받음
CPU 사용률:

스레드 1: 작업 ██──── 블로킹(대기) ─────██ 작업
스레드 2: 작업 ██── 블로킹(대기) ──██ 작업
스레드 3: 작업 ██─────── 블로킹(대기) ────────██ 작업

→ 대부분의 시간을 대기에 소비
→ CPU는 놀고 있는데 스레드는 점유 중
→ 자원 낭비

블로킹 vs 논블로킹 비교

항목블로킹논블로킹
호출 후완료까지 대기즉시 반환
스레드 상태멈춤 (Waiting)계속 실행
코드 흐름순차적, 직관적콜백/이벤트 기반
자원 효율낮음 (대기 중 점유)높음
코드 복잡도단순상대적으로 복잡

I/O 모델 4가지 분류

블로킹/논블로킹과 동기/비동기는 다른 개념이며, 조합됩니다.

                    동기                    비동기
              (결과를 직접 확인)       (결과를 통보받음)

블로킹        Sync + Blocking         거의 사용 안 함
(기다림)      ─────────────         ──────────────
              read()
              → 데이터 올 때까지 멈춤

논블로킹      Sync + Non-Blocking     Async + Non-Blocking
(안 기다림)   ─────────────         ──────────────
              read() → 없으면 즉시    aio_read()
              반환 → 반복 확인        → 완료되면 콜백 호출
              (폴링)
모델설명예시
Sync Blocking요청 후 결과 나올 때까지 대기Java InputStream.read()
Sync Non-Blocking요청 후 즉시 반환, 반복 확인 (폴링)Java NIO channel.read()
Async Non-Blocking요청 후 즉시 반환, 완료 시 통보Node.js fs.readFile()
[ Sync Blocking ]
스레드: 요청 ─── 대기 ─── 대기 ─── 결과 수신 ─── 처리

[ Sync Non-Blocking (폴링) ]
스레드: 요청 → "됐어?" → 아니 → "됐어?" → 아니 → "됐어?" → 응 → 처리
                ↑ 반복 확인하느라 CPU 낭비 가능

[ Async Non-Blocking ]
스레드: 요청 → 다른 작업 → 다른 작업 → 콜백 호출 → 처리
                ↑ 기다리지도, 확인하지도 않음
                  OS가 완료되면 알려줌

언어·프레임워크별 블로킹 처리 방식

환경블로킹 처리 전략
Java (전통)스레드 풀로 블로킹 I/O 처리, 요청당 스레드
Java NIOSelector + Channel로 논블로킹 I/O
Java Virtual Thread블로킹 코드를 작성하되 경량 스레드가 처리
Node.js이벤트 루프 + 논블로킹 I/O, 단일 스레드
Spring WebFluxReactor 기반 논블로킹 리액티브
Go고루틴 + 내부적으로 논블로킹 I/O
Nginx이벤트 기반 논블로킹

Java 전통 vs Virtual Thread

// 전통 — 블로킹이 스레드를 점유
Thread thread = new Thread(() -> {
    var data = db.query("SELECT ...");  // 스레드 블로킹
});
// 스레드 1개 = 수 MB 메모리, 수천 개가 한계

// Virtual Thread (Java 21) — 블로킹 코드 그대로, 경량 처리
Thread.startVirtualThread(() -> {
    var data = db.query("SELECT ...");  // 코드는 동일
    // 내부적으로 블로킹 시 OS 스레드 반납
    // → 수백만 개 동시 실행 가능
});

Node.js — 블로킹을 원천 차단

// Node.js는 메인 스레드가 하나이므로 블로킹하면 전체가 멈춤

// X 절대 하면 안 됨
const data = fs.readFileSync("file.txt");   // 메인 스레드 블로킹
// → 이 동안 모든 요청 처리 중단

// O 반드시 논블로킹 사용
const data = await fs.promises.readFile("file.txt");
// → 이벤트 루프가 다른 요청 처리 가능

핵심 정리

블로킹은 작업 완료까지 호출자의 실행이 멈추는 상태입니다. 코드가 직관적이라는 장점이 있지만, 대기 시간 동안 스레드를 점유하면서 아무 일도 못 하는 자원 낭비가 핵심 문제입니다. 이를 해결하기 위해 논블로킹 I/O, 이벤트 루프, 리액티브 프로그래밍, Virtual Thread 등 다양한 기술이 발전해왔으며, 현대 서버 아키텍처는 블로킹을 최소화하여 적은 스레드로 많은 요청을 처리하는 방향으로 진화하고 있습니다.

CSLOG에서 보기