ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Virtual Thread에 대해서
    카테고리 없음 2024. 1. 10. 12:22

    2023년 9월 19일에 릴리즈된 Java 21 는 Java 8 이후 세번째 LTS 버전이다(11, 17, 21). 이 버전에서는 많은 사람들이 기다리고 있는 가상 스레드 라는 기능이 추가되었다.

     

     

    Virtual Thread

    가상스레드란 전통적인 Java 스레드에 더하여 새롭게 추가되는 경량 스레드이다. 경량 스레드 모델이란 기존 언어의 스레드 모델보다 더 작은 단위로 실행 단위를 나눠 컨텍스트 스위칭 비용과 Blocking 타임을 낮추는 개념이다.

     

    Project Loom의 결과물로 추가된 기능으로 OS 스레드를 그대로 사용하지 않고 JVM 자체적으로 내부 스케줄링을 통해서 사용할 수 있는 경량의 스레드를 제공한다.

     

    Project Loom이란? 경량 스레드를 Java에 추가하기 위해서 가상 스레드를 비롯한 여러가지 기능들을 개발하는 프로젝트. Thread의 사전적 정의가 ‘실’이라는 점에서 실을 엮어 직물을 만든다는 뜻으로 Loom이란 단어를 사용했다.

     

     

     

     

    기존 Java Thread 모델

    기존 Java Thread는 platform thread이다. Platform Thread는 OS스레드를 감싼 것으로 platform thread를 사용하면 OS 스레드를 사용하는 것이다.

     

    Platform Thread는 Green Thread와 Native Thread로 나눌 수 있다. Green Thread는 VM과 라이브러리에서 관리하며 개발자가 직접 통제할 수 있는 논리적 Thread이고, Native Thread는 JVM이 OS 스레드에 직접 매핑하여 직접적으로 CPU를 사용하는 Thread이다. 각각 Thread는 User-level Thread, Kernel-level Thread로 불리기도 한다. 기존 자바 스레드는 platform thread 중에서도 native thread에 해당한다.

     

    기존 Java Thread 모델은 Java의 유저 스레드를 만들면 Java Native Interface(JNI)를 통해 커널 영역을 호출하여 OS가 커널 스레드를 생성하고 매핑하여 작업을 수행하는 형태였다. 이때 사용하는 스레드는 비용이 비싸기 때문에 스레드 풀을 사용하여 접근하는 방식을 사용했다.

     

    Java의 스레드는 I/O, interrupt, sleep과 같은 상황에 block/waiting 상태가 되는데, 이때 다른 스레드가 커널 스레드를 점유하여 작업을 수행하는 것을 컨텍스트 스위치(참고)라고 한다.

     

    이러한 스레드 모델은 기존 프로세스 모델을 잘게 쪼개 프로세스 내의 공통된 부분은 공유하면서, 작은 여러 실행단위를 번갈아 가면서 수행할 수 있도록 만들었다. 스레드는 프로세스의 공통 영역을 제외하고 만들어지기 때문에, 프로세스에 비해 크기가 작아서 생성 비용이 적고, 컨텍스트 스위칭 비용이 저렴했기 때문에 주목받아 왔다.

     

    그러나, 요청량이 급격하게 증가하는 서버 환경에서는 갈수록 더 많은 스레드 수를 요구하게 되었다. 스레드의 사이즈가 프로세스에 비해 작다고 해도, 생성할 수 있는 스레드 수는 한정적이다. 메모리가 제한된 환경에서는 생성할 수 있는 스레드 수의 한계가 있었고, 스레드가 많아지면서 컨텍스트 스위칭 비용도 기하급수적으로 늘어났다.

     

    또한 스레드 풀에 존재하는 스레드가 CPU를 가지고 요청을 처리할 때 네트워크 요청이나 파일 쓰기 같은 I/O작업을 만나면 CPU를 OS에 반환하고 실행할 수 없는 상태(Non-Runnable)가 된다.

     

    이런 한계를 겪던 서버는 더 많은 요청 처리량과 컨텍스트 스위칭 비용을 줄여야 했는데, 이를 위해 나타난 스레드 모델이 경량 스레드 모델인 Virtual Thread 이다.

     

     

     

     

    Virtual Thread 모델

    Virtual Thread는 기존 Java의 스레드 모델과 달리, 플랫폼 스레드와 가상 스레드로 나뉜다. 플랫폼 스레드 위에서 여러 Virtual Thread가 번갈아 가며 실행되는 형태로 동작한다. 마치 커널 스레드와 유저 스레드가 매핑되는 형태랑 비슷하다.

     

    가상스레드는 OS스레드를 감싼 구조가 아니기 때문에 가상 스레드 풀 없이 사용하고 JVM 자체적으로 가상스레드를 OS 스레드와 연결하는 스케줄링한다. 이 작업을 mount/unmount라고 한다. 가상스레드가 mount되는 플랫폼 스레드는 Carrier 스레드라고 한다.(가상 스레드를 실제 OS 스레드로 연결해준다는 의미)

     

    Platform Thread의 기본 스케줄러는 ForkJoinPool을 사용한다. 스케줄러는 platform thread pool을 관리하고, Virtual Thread의 작업 분배 역할을 한다.

     

    스케줄러를 사용함으로써 다음의 이점을 얻을 수 있다.

    기존의 스레드는 Blocking 이 발생하면 그냥 기다려야 했는데, 가상 스레드는 Blocking 이 발생하면 내부의 스케줄링을 통해서 실제 작업을 처리하는 Carrier 스레드는 다른 가상 스레드의 작업을 처리하면 된다. 따라서 Non-blocking 이 누리는 장점을 동일하게 누릴 수 있다.

     

    여기서 가장 큰 특징은 Virtual Thread는 컨텍스트 스위칭 비용이 저렴하다는 것이다.

      Thread Virtual Thread
    Stack 사이즈 ~2MB ~10KB
    생성시간 ~1ms ~1µs
    컨텍스트 스위칭 ~100µs ~10µs

     

    Thread는 기본적으로 최대 2MB의 스택 메모리 사이즈를 가지기 때문에, 컨텍스트 스위칭 시 메모리 이동량이 크다. 또한 생성을 위해선 커널과 통신하여 스케줄링해야 하므로, 시스템 콜을 사용하기 때문에 생성 비용도 적지 않다.

     

    하지만 Virtual Thread는 JVM에 의해 생성되기 때문에 시스템 콜과 같은 커널 영역의 호출이 적고, 메모리 크기가 일반 스레드의 1%에 불과하다. 따라서 Thread에 비해 컨텍스트 스위칭 비용이 적다.

     

     

     

     

    가상 스레드 사용 시 주의사항

    1. 기존 스레드 방식대로 풀링 사용하지 않기
      • 풀링은 고가의 리소스를 공유하기 위한 것이다.
      • 가상 스레드는 생성 비용이 싸기 때문에 굳이 풀링할 필요가 없다.
      • 필요할 때마다 생성하고 GC에 의해 소멸되도록 하자.
      • 동시성 제한을 위해서는 세마포어를 사용해야 한다.
    2. CPU bound 작업엔 비효율
      • IO작업 없이 CPU 작업만 수행하는 것은, 플랫폼 스레드만 사용하는 것보다 Virtual Thread가 성능이 떨어진다.
      • 컨텍스트 스위칭이 빈번하지 않은 환경이라면 기존 스레드 모델이 이득이다.
    3. Thread local
      • Thread local은 현재 스레드의 실행과 연관된 데이터들을 다루는 기법으로 캐싱, 파라미터 숨기기 등 다양한 목적으로 사용되고 있다.
      • 스레드 로컬은 메모리를 많이 사용할 수 있고 변경할 수 있기 때문에 스레드에 안전하지 않아 피하는 것이 좋다.

     

     

     

     


    참고

    댓글

Designed by Tistory.