티스토리 뷰

반응형

들어가며

현대 개발에서는 동시성과 병렬성을 고려하지 않고 시스템을 설계하기 어렵다.
이 때문에 많은 언어와 프레임워크가 효율적인 병렬 처리 방식을 제공하고 있다.
대표적으로 다음과 같은 모델들이 있다:

 

  • Java의 가상 스레드
  • Kotlin의 코루틴
  • Reactive Programming
  • Go의 고루틴(Goroutine)

이들 각각은 스타일과 구현은 다르지만, 공통된 목표를 가지고 있다.
바로, IO 작업을 효율적으로 처리하는 것이다.

 

IO 바운드 작업에 강한 병렬 모델들

IO 작업(예: DB 쿼리, 파일 읽기, 외부 API 호출 등)은 대기 시간이 길다.
전통적인 스레드 기반 구조에서는 IO 대기 중에도 스레드를 점유하기 때문에 스레드를 낭비하게되고, 많은 요청을 효율적으로 처리하기 어렵다.

그래서 위에서 언급한 병렬 모델들은 다음과 같은 방식으로 이 문제를 해결한다:

 

가상 스레드 (Java) 수만 개의 경량 스레드를 제공 기존 블로킹 코드 그대로 사용 가능
코루틴 (Kotlin) 협력적 스케줄링 기반의 경량 쓰레드 디스패처로 스레드 관리 가능
Reactive (Reactor, RxJava) 논블로킹 스트림 기반 처리 백프레셔, 스케줄러로 세밀한 제어
고루틴 (Go) M:N 스케줄링을 통한 경량 스레드 런타임이 OS 스레드에 자동 매핑

 

 

하지만 CPU 바운드 작업은 다르다

CPU 바운드 작업(예: 복잡한 계산, 압축, 암호화 등)은 IO와 다르게 CPU를 지속적으로 사용한다.
즉, 물리적인 CPU 코어 수만큼만 병렬 처리가 가능하다. (그래서 코루틴의 Dispatchers.Default의 스레드풀은 코어수 만큼 생성된다.)

경량 스레드를 아무리 많이 만들어도, CPU 바운드 작업을 처리하는 순간부터 물리적 코어 수가 병목이 되며, 스케줄링에 부작용이 발생할 수 있다.

각 모델에서 주의할 점

가상 스레드 전용 플랫폼 스레드 풀을 구성해야 함
코루틴 Dispatchers.Default 사용 (CPU 최적화 디스패처)
Reactive .publishOn(Schedulers.parallel()) 등을 사용하여 CPU용 스레드풀 분리
Go GOMAXPROCS 조정 및 워커 풀 분리 권장

모델CPU 바운드 대응 방법

 

핵심은 "CPU 바운드 작업은 별도 고려 대상"이라는 것

IO 바운드 작업은 경량 스레드 모델이 큰 효과를 발휘할 수 있다.
하지만 CPU 바운드 작업은 각 모델마다 별도의 처리가 필요하며,
적절한 스레드풀 또는 디스패처를 명시하지 않으면 성능 저하 또는 병목이 생길 수 있다.

 

결론

병렬 처리 모델들은 IO 효율화에 최적화되어 있다.
하지만 CPU 바운드 작업을 함께 처리하려면 반드시 별도 주의가 필요하다.

아무리 고루틴, 코루틴, 가상 스레드가 가볍다 해도, 물리적 자원의 한계를 넘어서진 못한다.

시스템 설계 시,

  • 무엇이 IO 바운드이고,
  • 무엇이 CPU 바운드인지
    를 명확히 구분하고, 각각에 적절한 실행 환경을 설계하는 것이 병렬 처리 성능을 끌어올리는 핵심이다.
반응형
댓글