(OS) Thread Per Request vs Thread Pool 모델 정리 (쓰레드 비교)
불광동 물주먹
2025. 4. 15. 22:48
Thread Per Request vs Thread Pool 모델 정리
1. Thread Per Request 모델
서버에 요청이 들어올 때마다 새로운 스레드를 생성해 처리하고, 요청 처리가 끝나면 해당 스레드를 버리는 방식입니다.
⚠️ 문제점
스레드 생성 비용: 스레드 생성과 소멸은 커널 개입이 필요한 작업으로 비용이 큽니다. (컨텍스트 구조 초기화, 스택 메모리 확보 등)
요청 폭주 시 병목: 요청이 처리 속도보다 빠르게 들어올 경우, 새로운 스레드가 계속 생성되고 이로 인해 스레드 수 급증.
컨텍스트 스위칭 빈도 증가 → CPU 오버헤드 발생
메모리 고갈 가능성 → OutOfMemoryError 또는 서버 응답 불능
예측 불가능성: 자원 소모가 요청 수에 비례하여 늘어나므로, 대량 요청에 대한 대비가 어렵다.
사진 출처 - 유튜브 쉬운코드
2. Thread Pool 모델
요청마다 새 스레드를 생성하는 대신, 미리 생성된 일정 수의 스레드들을 재사용하는 방식입니다.
작동 방식
들어오는 요청은 내부 큐에 적재
사용 가능한 스레드가 큐에서 작업을 꺼내 실행
작업 종료 후 해당 스레드는 풀로 복귀되어 대기
장점
스레드 재사용으로 생성/소멸 비용 절감
자원 사용량을 제어하여 안정적인 서버 운영 가능
과도한 컨텍스트 스위칭 방지 → CPU 효율 개선
사진 출처 - 유튜브 쉬운코드
3. 실전 적용 시 고려사항
1) 스레드 풀 크기 설정 기준
CPU Bound Task 계산 중심 로직 → CPU 사용 비중 큼 → 코어 수와 동일하거나 소폭 많은 값 권장 예: Runtime.getRuntime().availableProcessors()
IO Bound Task DB/네트워크 등 외부 자원을 기다리는 시간이 많음 → 스레드 수를 더 많이 설정해도 무방 예: 코어 수의 2~4배 (경험적으로 튜닝 필요)
💡 Java의 Executors.newFixedThreadPool(n) 또는 ThreadPoolExecutor 사용 시, 명시적으로 설정할 수 있음.
java 예시
newFixedThreadPool 메서드로 쓰레드풀 10개 생성
큐사이즈는???
1. corePoolSize와 maximumPoolSize가 동일하게 nThreads로 설정 → 고정된 수의 스레드를 사용하는 풀 (Fixed Thread Pool)
2. 큐는 new LinkedBlockingQueue<>() → 기본 생성자 사용
3. LinkedBlockingQueue 자기 자신을 호출하는 생성자 오버로딩 (this() 사용)
4. capacity 값을 Integer.MAX_VALUE로 설정
→ 약 21억(2^31 - 1) 개의 작업을 저장할 수 있는 사실상 무제한 큐
주의점!!!!
Executors.newFixedThreadPool() 사용 시:
스레드 개수는 제한되어 있지만
큐는 사실상 무제한 (Integer.MAX_VALUE)
즉, 요청이 과도하게 밀릴 경우 메모리 과다 사용으로 인해 OOM(Out Of Memory) 발생 위험 있음
대안 (큐 개수 설정)
ExecutorService pool = new ThreadPoolExecutor(
10, // corePoolSize
10, // maximumPoolSize
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1000), // 제한된 큐 크기
new ThreadPoolExecutor.CallerRunsPolicy() // 거절 정책 설정
);
2) 작업 큐(Work Queue)의 크기 제한
큐 사이즈가 무제한이면: 요청이 폭주할 경우 큐에 계속 쌓이면서 메모리 부족 위험 발생
큐 사이즈가 제한되면: 큐가 가득 찼을 때 RejectedExecutionHandler로 대처 가능
예: CallerRunsPolicy, AbortPolicy, DiscardPolicy 등
큐 정책과 함께 백프레셔(Backpressure) 또는 최대 요청 수 제한 등의 보호 장치도 고려 필요