개발자

프로젝트 룸 : 새로운 자바 동시성 모델 이해하기

Matthew Tyson | InfoWorld 2023.12.27
룸(Loom)은 자바 및 JVM 생태계의 최신 프로젝트다. 오픈JDK에서 호스팅하는 룸의 목적은 기존 자바 동시성 모델의 한계를 해결하는 것이다. 특히 쓰레드의 더 가벼운 대안과 함께 이를 관리하기 위한 새로운 언어 구조도 제공한다. 이미 룸에서 가장 중대한 부분이 된 가상 쓰레드는 자바 21부터 JDK에 포함된다.
 
여기에서는 프로젝트 룸의 개요와 룸을 통해 자바 동시성이 어떻게 현대화되는지 살펴본다.
 
ⓒ Getty Images Bank
 

자바의 가상 쓰레드

전통적인 자바 동시성은 예시 1에서 볼 수 있듯이 Thread와 Runnable 클래스로 관리된다.


예시 1. 전통적인 자바로 쓰레드 실행하기
Thread thread = new Thread("My Thread") {
      public void run(){
        System.out.println("run by: " + getName());
      }
   };
   thread.start();
   System.out.println(thread.getName()); 
 
전통적인 자바 동시성은 간단한 사례에서는 이해하기가 쉽고, 쓰레드 작업과 관련해서 자바가 제공하는 지원도 풍부하다.
 
단점은 자바 쓰레드가 운영체제(OS)의 쓰레드에 직접 매핑된다는 것이다. 이로 인해 동시 자바 애플리케이션의 확장성이 엄격히 제한된다. 이는 애플리케이션 쓰레드와 OS 쓰레드 간의 일 대 일 관계를 의미하며, 최적의 배치를 위해 쓰레드를 조직할 메커니즘도 없다. 예를 들어 상호 밀접하게 관련된 쓰레드가 동일한 프로세서에서 힙을 공유해는 데서 이점을 얻을 수 있음에도 불구하고 서로 다른 프로세스를 공유하는 상황이 발생할 수 있다.
 
룸의 변화가 얼마나 큰지 감을 잡기 위해 이렇게 생각해 보자. 현재 자바 스레딩은 고사양 서버에서도 최대 수천 개의 쓰레드로 계산된다. 룸은 이 한계를 수백만 개의 쓰레드로 확장하는 안을 제시하는 것이다. 표준 요청 처리는 쓰레드 수와 연결되므로 이 변화가 자바 서버 확장성에 미치는 영향은 막대하다.
 
해결 방법은 자바 쓰레드가 기반 OS 쓰레드로부터 추상화되고 JVM이 이 둘 사이의 관계를 더 효과적으로 관리할 수 있는 일종의 가상 스레딩을 채택하는 것이다. 프로젝트 룸은 이를 위해 새로운 가상 쓰레드 클래스를 도입한다. 새 VirtualThread 클래스의 API 표면은 일반적인 쓰레드와 동일하므로 손쉽게 마이그레이션할 수 있다.
 

연속성과 구조적 동시성

연속성은 가상 스레딩의 기반이 되는 저수준 기능이다. 기본적으로 연속성은 JVM이 실행 흐름을 파킹 및 재시작할 수 있게 해준다.
 
프로젝트 룸 제안에는 다음과 같은 문구가 있다.
 

연속성 구현, 그리고 사실 이 프로젝트 전체의 주된 기술적인 사명은 커널 쓰레드의 일부가 아닌 콜스택을 캡처, 저장 및 재개할 수 있는 기능을 핫스팟(HotSpot)에 추가하는 것이다.

 
룸의 또 다른 특징인 구조적 동시성은 동시성에 대한 쓰레드 의미 체계의 대안을 제공한다. 구조적 동시성의 주된 개념은 비동기 흐름을 처리하기 위한 동기 구문을 제공하는 것이다(자바스크립트의 async 및 await 키워드와 비슷함). 이는 간단한 동시 작업을 쉽게 표현할 수 있게 해주므로 자바 개발자에게는 매우 반가운 부분이다.
 
바이트코드 조작을 통해 자바에 가벼운 스레딩을 도입한 퀘이사(Quasar)를 접해 본 사람이라면 퀘이사의 기술 책임자인 론 프레슬러를 알 것이다. 현재 오라클에서 룸을 이끌고 있는 프레슬러는 구조적 동시성을 다음과 같이 설명했다.
 

구조적 동시성은 구조적 프로그래밍의 원칙을 동시 코드에 적용하는 패러다임으로, 동시 프로그래밍의 가장 큰 골칫거리로 꼽히는 오류 처리와 취소를 깔끔하게 처리하는 동시 코드를 더 쉽게 작성할 수 있게 해준다. 우리는 JDK 21에서 구조적 프로그래밍을 JDK에 도입하는 프리뷰 API인 StructuredTaskScope를 제공했다. 가상 쓰레드는 프로그램의 모든 동시 작업에 각자 고유한 쓰레드가 있음을 의미하므로 가상 쓰레드와 StructuredTaskScope는 서로 아주 잘 어울린다. StructuredTaskScope는 올바른 동시 코드를 쉽게 작성할 수 있게 해주는 것 외에 구조적 관찰(structured observation) 기능, 즉 쓰레드 간의 관계를 캡처하는 쓰레드 덤프를 제공한다.

 

가상 쓰레드의 대안

룸에 대해 더 자세히 알아보기 전에 지금까지 자바의 동시성을 위한 다양한 접근 방식이 제안되었다는 점에 주목해 보자. 그동안 제안된 접근 방식은 대체로 비동기 프로그래밍 모델에 해당한다. CompletableFutures 및 논블로킹(non-blocking) IO와 같이 쓰레드 사용의 효율성을 개선하는 방법으로 문제를 피해가는 방법도 있고, RX자바(리액티브X의 자바 구현)와 같은 전면적인 비동기 대안도 있다.
 
RX자바는 동시성에 대한 강력하고 잠재적으로 고성능을 낼 수 있는 접근 방식이지만 단점이 있다. 특히 자바 개발자들이 전통적으로 사용해온 개념 모델과 상당한 차이가 있다. 또한 RX자바의 성능은 가상 머신 계층에서 가상 쓰레드를 관리함으로써 달성할 수 있는 이론적 성능에 미치지 못한다.
 

자바의 새로운 VirtualThread 클래스

앞서 언급했듯이 새로운 VirtualThread 클래스는 가상 쓰레드를 나타낸다. 내부적으로는 복잡한 비동기 동작이 수행된다. 그냥 언어 수준에서 리액티브X와 같은 것을 도입하지 않고 굳이 어려운 길을 가는 이유가 무엇일까? 답은 개발자가 더 쉽게 이해하도록 하기 위해, 그리고 기존 코드 환경을 더 쉽게 이동하도록 하기 위해서다. 예를 들어 데이터 저장소 드라이버를 새 모델로 더 쉽게 전환할 수 있다.
 
예시 2에서 가상 쓰레드 사용의 간단한 예를 볼 수 있다. 보면 알겠지만 기존 Thread 코드와 매우 유사하다(코드는 오라클의 룸 및 가상 쓰레드 입문서에서 발췌).


예시 2. 가상 쓰레드 만들기
Thread.startVirtualThread(
  () -> {
    System.out.println("Hello World");
  }
);
 
이 간단한 예제의 이면에는 스케줄링에 대한 폭넓은 고려가 반영돼 있다. 이러한 메커니즘은 아직 확정되지는 않았지만 룸 제안에 관련 개념에 대한 개요가 잘 정리돼 있다.
 
룸의 가상 쓰레드에서 한 가지 중요한 점은 전체 자바 시스템에 어떤 변경이 필요하든 그 변경이 기존 코드를 손상시키지 말아야 한다는 것이다. 기존 스레딩 코드는 앞으로도 완전히 호환된다. 가상 쓰레드를 사용할 수 있지만 반드시 사용해야 하는 것은 아니다. 이러한 하위 호환성을 달성한다는 것은 매우 힘든 일인 만큼 룸 개발 팀이 가장 많은 시간을 투자하는 부분이기도 하다.
 

연속성을 사용한 하위 수준 비동기화

가상 쓰레드에 대해 살펴봤으니 이제 아직 개발 중인 연속성 기능에 대해 살펴보자. 룸은 가상 쓰레드와 구조적 동시성에서 연속성을 지원한다. 개발자가 사용할 수 있는 공개 API로 연속성이 제공된다는 이야기도 있다. 그렇다면 연속성이란 무엇인가?
 
상위 수준에서 연속성은 프로그램의 실행 흐름을 코드로 표현한 것이다. 즉, 연속성은 개발자가 함수를 호출해서 실행 흐름을 조작할 수 있도록 한다. 룸 문서에서 가져온 예시 3의 코드를 보면 연속성이 어떻게 작동하는지 감을 잡을 수 있을 것이다.

예시 3. 연속성 예제
foo() { // (2)
  ...
  bar()
  ...
}
bar() {
  ...
  suspend // (3)
  ... // (5)
}
main() {
  c = continuation(foo) // (0)
  c.continue() // (1)
  c.continue() // (4)

실행 흐름을 살펴보자(주석 처리된 번호를 기준으로 설명).
 
  • (0) 연속성이 생성됨. foo 함수부터 시작
  • (1) 연속성의 진입점으로 제어 전달
  • (2) 다음 중지 지점인 (3)까지 실행
  • (3) (1)에서 원점으로 제어를 되돌림
  • (4) 실행되어 연속성에서 continue를 호출하고, 흐름은 중지된 (5)로 되돌아감
 

꼬리 호출 제거

룸의 또 다른 공식적인 목표는 꼬리 호출(tail-call) 제거다(꼬리 호출 최적화라고도 함). 꼬리 호출 제거는 꽤 난해한 요소다. 핵심 개념은 시스템이 가능한 모든 상황에서 연속성을 위한 새로운 스택 할당을 피할 수 있다는 것이다. 이 경우 프로세스의 각 단계를 위해 이전 스택을 저장해서 호출 스택이 풀릴 때 사용할 수 있도록 해야 하므로 연속성을 실행하기 위해 필요한 메모리의 양은 계속 누적되지 않고 일정하게 유지된다. 
 

룸의 다음 단계는?

룸이 지금까지 제공해온 부분만 해도 설명할 내용이 이미 상당히 많지만 앞으로 더 많은 것이 계획돼 있다. 론 프레슬러에게 향후 로드맵에 대해 물었다.
 
단기적으로는 완전히 투명한 가상 쓰레드 도입을 가로막는 가장 큰 장애물이라고 할 수 있는 synchronized로 인한 피닝(pinnng) 문제를 해결하는 데 주력하고 있다. 현재 동기화된 블록 또는 메서드 내부를 보면 일반적으로는 기반 OS 쓰레드를 해제하는 IO 작업이 오히려 OS 쓰레드를 차단한다. 피닝이라고 하는 이 현상이 자주, 긴 시간에 걸쳐 발생하면 가상 쓰레드가 가진 확장성 측면의 이점이 손상될 수 있다.

현재 임시방편은 JDK의 관찰 툴을 사용해 이에 해당하는 경우를 파악해서 피닝 문제가 없는 java.util.concurrent 잠금으로 대체하는 것이다. 현재 synchronized가 피닝을 유발하지 않도록 하기 위한 작업을 진행 중이므로 앞으로는 이 같은 우회로가 필요 없게 될 것이다. 또한 가상 쓰레드에 의한 IO 작업의 스케줄링 효율성을 높여 성능을 개선하는 작업도 하고 있다.
 
중기적으로는 가능한 부분에 io_uring을 통합해서 네트워킹 작업 외에 파일 시스템 작업을 위한 확장도 제공하려고 한다. 또한 맞춤형 스케줄러도 구상하고 있다. 가상 쓰레드 스케줄은 현재 범용 서버에 적합한 스케줄러를 통해 동작하는데, 비범용적 용도에는 다른 스케줄링 알고리즘이 필요할 수 있으므로 플러그인 형태의 맞춤형 스케줄러를 지원하고자 한다.
 
더 멀리 보면, 채널(차단 대기열(blocking queue)과 비슷하지만 명시적 닫기와 같은 부가적인 작업이 포함됨) 추가를 생각 중이며, 파이썬과 같이 반복자를 쉽게 작성할 수 있게 해주는 생성기를 추가할 가능성도 있다.
 

룸과 자바의 미래

일반적으로 룸과 자바는 웹 애플리케이션 구축 용도에 편중돼 있다. 물론 자바는 그 외의 많은 분야에도 사용되며, 룸의 아이디어는 다양한 애플리케이션에서 유용함을 발휘할 수 있다. 쓰레드 효율성이 대폭 향상되고 서로 경쟁하는 여러 요구를 처리하기 위한 리소스 요구사항이 비약적으로 줄어들면 당연히 서버의 처리량이 증가하는 결과를 얻게 된다. 요청 및 응답 처리의 개선은 현재와 미래의 자바 애플리케이션 생태계 전체에 최종적인 이익이다.
 
물론 큰 목표를 둔 모든 새로운 프로젝트가 그렇듯이 룸에도 풀어야 할 숙제는 있다. 가상 또는 기타 쓰레드의 정교한 인터리빙은 항상 복잡한 문제이며 룸의 동시성 모델을 처리하기 위해 정확히 어떤 라이브러리 지원과 설계 패턴이 등장하게 될지도 지켜봐야 할 일이다.
 
앞으로 프로젝트 룸이 자바의 메인 브랜치에 포함되고 실제 사용에 대응해서 발전하는 과정은 흥미로운 볼거리가 될 것이다. 이 과정이 진행되면서 룸의 새로운 시스템이 가진 장점이 개발자들이 사용하는 인프라(제티(Jetty), 톰캣(Tomcat)과 같은 자바 애플리케이션 서버)에 적용되면 자바 생태계에 획기적인 변화가 일어날 수도 있다.
 
자바, 그리고 서버 부문에서 자바의 주 경쟁 상대인 Node.js의 성능은 막상막하다. 일반적인 웹 애플리케이션 사용 사례에서 자바의 성능이 비약적으로 높아진다면 향후 이 판도가 완전히 바뀔 수 있다.
editor@itworld.co.kr 
Sponsored

회사명 : 한국IDG | 제호: ITWorld | 주소 : 서울시 중구 세종대로 23, 4층 우)04512
| 등록번호 : 서울 아00743 등록발행일자 : 2009년 01월 19일

발행인 : 박형미 | 편집인 : 박재곤 | 청소년보호책임자 : 한정규
| 사업자 등록번호 : 214-87-22467 Tel : 02-558-6950

Copyright © 2024 International Data Group. All rights reserved.