왜 이 글을 쓰는가
Spring은 요청마다 스레드를 하나씩 쓴다.
DB 커넥션 풀은 스레드 수를 기준으로 튜닝한다.
Spring WebFlux는 스레드를 최소화해서 성능을 끌어올린다.
이 모든 것의 전제가 되는 질문이 하나 있다.
"프로세스와 스레드가 정확히 뭐가 다른가?"
이 개념이 흐릿하면 멀티스레드 버그가 왜 생기는지, 컨텍스트 스위칭 비용이 왜 문제가 되는지 설명할 수 없다.
1. 프로세스란
프로세스는 실행 중인 프로그램이다.
프로그램은 디스크에 저장된 코드 파일이고, 그것을 OS가 메모리에 올려 실행하기 시작하면 프로세스가 된다.
프로세스의 메모리 구조
각 프로세스는 이 메모리 공간을 독립적으로 가진다.
프로세스 A는 프로세스 B의 메모리를 직접 읽거나 쓸 수 없다.
OS가 가상 메모리로 각 프로세스를 격리하기 때문이다.
프로세스가 가진 것
•
독립된 메모리 공간 (Code, Data, Heap, Stack)
•
프로세스 ID (PID)
•
열린 파일 디스크립터
•
자식 프로세스
•
CPU 레지스터 상태 (PC, SP 등)
2. 스레드란
스레드는 프로세스 안에서 실제로 실행되는 작업 단위다.
하나의 프로세스 안에 여러 스레드가 존재할 수 있다.
스레드가 공유하는 것 vs 독립적으로 가지는 것
구분 | 내용 |
공유 | Code 영역, Data 영역, Heap 영역, 열린 파일 |
독립 | Stack, 레지스터(PC, SP 등), 스레드 ID |
스레드는 같은 프로세스 내 메모리를 공유한다.
이게 스레드의 장점이자 위험이다.
왜 스레드를 쓰는가
프로세스를 여러 개 만들면 각각 독립된 메모리가 필요하고, 프로세스 간 통신(IPC)도 복잡하다.
스레드는 메모리를 공유하므로 데이터를 주고받기 쉽고, 생성 비용도 훨씬 작다.
3. 프로세스 vs 스레드 비교
항목 | 프로세스 | 스레드 |
메모리 | 독립 | 공유 (Heap, Data, Code) |
생성 비용 | 높음 | 낮음 |
통신 방법 | IPC (파이프, 소켓 등) | 공유 메모리 직접 접근 |
격리성 | 강함 (하나가 죽어도 다른 프로세스 영향 없음) | 약함 (하나가 죽으면 프로세스 전체 위험) |
컨텍스트 스위칭 비용 | 높음 | 낮음 |
예시 | Chrome 탭마다 프로세스 분리 | Spring 요청 처리 스레드 |
4. 컨텍스트 스위칭
CPU는 한 번에 하나의 작업만 처리한다.
하지만 우리는 여러 프로그램이 동시에 돌아가는 것처럼 느낀다.
이게 가능한 이유가 컨텍스트 스위칭(Context Switching) 이다.
컨텍스트 스위칭이란
OS 스케줄러가 CPU를 점유하는 작업을 빠르게 교체하는 것이다.
교체할 때 현재 작업의 상태(컨텍스트)를 저장하고, 다음 작업의 상태를 복원한다.
컨텍스트란 무엇인가
프로그램 카운터(PC), 스택 포인터(SP), 범용 레지스터 값 등
CPU가 작업을 재개하는 데 필요한 모든 상태 정보다.
이 정보는 PCB(Process Control Block) 또는 TCB(Thread Control Block) 에 저장된다.
컨텍스트 스위칭 비용
컨텍스트 스위칭은 공짜가 아니다.
•
레지스터 저장/복원 시간
•
캐시 무효화: CPU 캐시(L1, L2)에 올라와 있던 데이터가 날아감
•
TLB 플러시: 프로세스 전환 시 가상 주소 변환 캐시도 초기화
스레드 전환은 같은 프로세스 내에서 일어나므로 메모리 공간이 같다.
그래서 프로세스 전환보다 비용이 작다.
언제 컨텍스트 스위칭이 발생하는가
원인 | 설명 |
타임슬라이스 소진 | OS가 할당한 CPU 시간이 끝남 |
I/O 대기 | 파일 읽기, 네트워크 응답 대기 등 |
sleep() 호출 | 스레드가 스스로 CPU 양보 |
인터럽트 | 하드웨어 신호로 OS가 개입 |
우선순위 선점 | 더 높은 우선순위 스레드 등장 |
5. 멀티프로세스 vs 멀티스레드
멀티프로세스
•
하나가 죽어도 다른 프로세스에 영향 없음
•
메모리 사용량이 많고 통신이 복잡
•
예: Chrome (탭마다 별도 프로세스로 격리)
멀티스레드
•
메모리 효율 높음, 통신 단순
•
하나의 스레드가 공유 데이터를 잘못 건드리면 전체 프로세스에 영향
•
동기화 문제 (Race Condition, Deadlock) 발생 가능
•
예: Spring의 Tomcat 스레드 풀
6. 실무 연결 — Spring과 스레드
Spring MVC의 Thread-per-Request 모델
Tomcat은 스레드 풀을 유지한다. 기본 최대 200개.
요청이 들어오면 풀에서 스레드를 꺼내 처리하고, 완료되면 반환한다.
문제: 스레드가 I/O 대기 중(DB 쿼리, 외부 API 호출)이면 CPU는 놀고 있는데 스레드는 점유 중이다.
스레드가 모두 소진되면 새 요청은 대기한다.
Spring WebFlux의 이벤트 루프 모델
I/O 대기 동안 스레드를 점유하지 않는다.
적은 스레드로 많은 요청을 처리할 수 있다.
컨텍스트 스위칭 비용도 줄어든다.
단점: 코드가 복잡해지고, 블로킹 코드가 섞이면 오히려 성능이 나빠진다.
DB 커넥션 풀 튜닝과의 연결
HikariCP 기본 설정에서 커넥션 풀 크기는 보통 CPU 코어 수 × 2 수준을 권장한다.
스레드가 200개여도 동시에 CPU를 쓸 수 있는 스레드는 코어 수만큼이고,
나머지는 컨텍스트 스위칭 대기 중이기 때문이다.
정리
개념 | 핵심 |
프로세스 | 독립된 메모리를 가진 실행 단위. 격리성 강함 |
스레드 | 프로세스 내 실행 단위. Heap/Data 공유, Stack 독립 |
컨텍스트 스위칭 | CPU 점유 작업 교체. 레지스터 저장/복원 + 캐시 비용 |
프로세스 전환 | 메모리 공간 전환 포함, 비용 큼 |
스레드 전환 | 같은 메모리 공간, 비교적 비용 작음 |
Spring MVC | 요청마다 스레드 1개. 스레드 수가 처리량 상한 |
Spring WebFlux | 소수 스레드 + 이벤트 루프. I/O 대기 중 스레드 해방 |
다음 글에서는 OS의 I/O 모델(Blocking / Non-Blocking / Async)을 다룬다.
이 글에서 나온 "I/O 대기 중 스레드 점유"가 왜 문제인지, 그 해결책이 무엇인지 이어진다.

