1. 스레드(Thread)
운영체제(OS)에서 실행되는 가장 작은 실행 단위
- 하나의 프로세스는 여러 개의 스레드를 가질 수 있고, 이 스레드들은 같은 메모리 공간을 공유
- CPU는 여러 스레드를 빠르게 전환(context switching)하면서 실행
- 스레드가 많아질수록 전환 비용이 커지고, 메모리 사용량도 증가
ex) Spring MVC는 요청 1개당 스레드 1개를 할당. 사용자가 동시에 1,000명 들어오면 스레드도 1,000개가 생기고, 이 상태에서 context switching 비용이 급격히 늘어나 서버 리소스를 많이 잡아먹게 된다.
2. 동시성(Concurrency)과 병렬성(Parallelism)
동시성(Concurrency): 여러 작업이 동시에 실행되는 것처럼 보이는 상태
- 실제로는 CPU가 작업을 매우 빠르게 전환해서 한꺼번에 실행되는 것처럼 보인다.
- I/O 바운드 상황(네트워크 호출, 파일 읽기, DB 쿼리)에서 효과적임
예: 하나의 CPU 코어가 0.1초마다 네트워크 요청을 번갈아 처리하면, 여러 요청이 동시에 진행되는 것처럼 보인다.
병렬성(Parallelism): 실제로 여러 CPU 코어에서 동시에 작업이 실행
- 연산량이 많은 CPU 바운드 작업(대규모 데이터 계산, 이미지 처리 등)에 적합
예: 4코어 CPU가 있을 때, 코어 4개가 각각 다른 연산을 동시에 수행하면 병렬 처리
비동기 프로그래밍은 주로 “동시성”을 극대화해서, 한정된 스레드로도 많은 요청을 처리할 수 있게 만드는 게 핵심
3. Blocking vs Non-Blocking
Blocking: 한 작업이 끝날 때까지 스레드가 대기
- 스레드가 대기 상태로 묶이기 때문에 다른 일을 못함
- 단순하고 직관적이지만, 요청이 많을 때 스레드 낭비가 심함
예: DB 쿼리를 날리고 결과가 올 때까지 스레드가 놀고 있는 상태
Non-Blocking: 요청을 보낸 스레드가 놀지 않고 다른 작업을 계속 처리
- 요청 결과가 준비되면 이벤트나 콜백으로 이어서 처리
- 스레드 효율을 높여서, 적은 스레드로도 수많은 요청을 처리 가능
ex) 파일 다운로드 요청을 보낸 뒤, 스레드는 다른 네트워크 요청을 처리하고 있다가, 파일이 다 받아지면 이벤트가 발생해 이어서 처리함
비동기(Async) 프로그래밍은 Non-Blocking을 적극적으로 활용하는 방식
4. 왜 중요한가
Reactor(Mono/Flux), Coroutine, Virtual Thread 같은 최신 기술들은 모두 결국 스레드를 얼마나 효율적으로 쓸 것인가 라는 같은 문제를 해결하려는 시도
- Reactor/WebFlux는 이벤트 루프 기반으로 적은 스레드로도 높은 동시성을 확보
- Coroutine은 경량 실행 단위를 만들어, 동기 코드처럼 보이는 비동기 코드를 작성하게 해준다.
- Virtual Thread는 기존 블로킹 API를 그대로 사용하면서도 수십만 개 스레드를 실행할 수 있도록 JVM이 직접 관리
즉, 스레드 / 동시성 / 블로킹 vs 논블로킹 개념을 확실히 이해해야, 각 기술이 왜 등장했고 언제 어떤 걸 써야 하는지 구분할 수 있음
정리
- 스레드는 OS 실행 단위이며, 많아지면 context switching 비용이 커진다.
- 동시성은 동시에 실행되는 것처럼 보이는 것, 병렬성은 실제 동시에 실행되는 것
- Blocking은 스레드가 대기하는 것이고, Non-Blocking은 스레드가 다른 일을 할 수 있음
- 비동기 프로그래밍은 Non-Blocking을 이용해 동시성을 효율적으로 확보하는 방식
