개요
지난 글에서 Virtual Thread가 어떻게 다른지, 어떠한 부분에서 연관되어 있는지 알아봤습니다.
이번 글에서는 Virtual Thread의 코드를 따라가보며 어떻게 동작하는지 살펴보겠습니다.
Virtual Thread의 LifeCycle
Virtual Thread의 라이프사이클을 요약하면 위의 그림과 같은 모습입니다.
Virtual Thread는 많은 상태를 가진 상태 머신이고, 이 흐름을 따라가면서 설명하도록 하겠습니다.
그림에 있는 상태 이외에도 더 많은 상태가 있지만, 그림에 나온 주요한 상태만을 간략하게 알아보겠습니다.
1. NEW, STARTED, RUNNABLE
Virtual Thread의 생성자 코드입니다.
Thread.ofVirtual을 통해 가상 스레드를 생성하면, 위의 생성자가 호출됩니다.
생성자 파라미터로 스케줄러, 이름, characteristics, task를 받습니다.
스케줄러의 경우, 위의 코드를 보았을 때 DEFAULT_SCHEDULER로 지정되어 있습니다.
코드를 따라가 보면, Virtual Thread클래스의 기본 스케줄러로 ForkJoinPool이 지정되어 있음을 알 수 있습니다.
name과 characteristics는 Thread에서 지정되어 내려오고, task는 사용자가 할 작업입니다.
스케줄러가 지정되고 VThreadContinuation객체가 생성됩니다.
VThreadContinuation객체에 runnable정보를 저장하는 모습을 볼 수 있습니다.
그리고 마지막으로, runContinuation필드를 설정합니다.
runContinuation에서는, 가상스레드가 run하기 전에 하는 작업을 볼 수 있습니다.
STARTED상태, RUNNABLE상태인 경우 RUNNING상태로 변경합니다.
이후 현재 runnable을 수행하고 작업이 끝나면 TERMINATED, 그렇지 않다면 YIELD하는 모습을 볼 수 있습니다.
그렇다면, new에서 started상태로는 어떻게 진행될까요?
start메서드에 그 답이 있습니다.
Virtual Thread가 일반 스레드와 같이 start메서드를 실행하면, 상태를 STARTED로 변경합니다.
이후 submitRunContinuation메서드를 실행합니다.
submitRunContinuation은 스케줄러에 runContinuation을 등록시키는 것을 볼 수 있습니다.
가상 스레드가 만들어지면 new상태가 되고
start메서드를 통해 started상태로 변화하고
runContinuation메서드를 통해 작업을 시작하면 running상태로 변화하는 것을 볼 수 있습니다.
2. RUNNING
스케줄러에 의해 run메서드가 실행되면, state를 먼저 체크합니다.
이후 mount작업을 진행합니다.
작업을 계속 수행하다가, 마지막에 unmount되고 terminated상태가 되는 것을 볼 수 있습니다.
그렇다면, mount에 대해서 알아야 할 필요가 있습니다.
mount는 상당히 간단합니다.
ForkJoinPool에 있는 carrierThread를 가져와 Virtual Thread를 등록합니다.
즉, Carrier Thread가 실제로 Virtual Thread를 실행하도록 작업을 얹는 것입니다.
반대로 unmount는 carrierthread를 가져와 Virtual Thread를 할당 해제합니다.
가상스레드가 running상태가 되면 run메서드를 실행하고,
carrier thread를 가져와 Virtual Thread를 할당하고 작업을 진행합니다.
이후 마지막에 carrier thread에서 Virtual Thread를 할당 해제하는 모습을 봤습니다.
3. PARKING
Virtual Thread 작업을 진행하던 도중, blocked, wait등의 상태가 되면 park메서드를 실행합니다.
thread.sleep의 경우 park메서드와 유사한 parkNanos메서드를 실행합니다.
park메서드에서는 yieldContinuation메서드를 실행하게 됩니다.
이후 thread가 다시 작업을 진행할 수 있다면 상태를 running으로 바꾸고 다시 작업을 진행합니다.
yieldContinuation메서드에서는 unmount를 진행하고 Virtual Thread를 메모리에서 할당 해제합니다.
Virtual Thread가 지금은 block상태이지만, 양보가 불가능한 경우가 있습니다.
이러한 때는, parkOnCarrierThread메서드가 실행됩니다.
이때는 해당 Virtual Thread를 pinned상태로 만듭니다.
이후 다시 실행 가능한 상태가 되면 running상태가 됩니다.
4. PARKED
Virtual Thread의 생성자에서 runContinuation메서드를 할당했습니다.
이 때, 아래의 코드를 볼 수 있었습니다.
작업을 진행하고, 작업이 종료되면 Terminate상태로, 아직 진행중이면 afterYield메서드를 실행합니다.
afterYield 메서드의 동작은 두 가지로 나뉩니다.
먼저 첫번째, parking상태인 경우입니다.
unmount작업이 종료된 경우, 해당 스레드는 parked상태입니다.
Virtual Thread가 완전히 parked되면 다시 작업 가능한 상태로 만들어야 합니다.
따라서, Virtual Thread의 상태를 runnable로 바꾼 뒤 스케줄러에 등록합니다.
CarrierThread가 ForkJoinPool의 스레드라면, lazySubmitRunContinuation메서드를 실행합니다.
해당 메서드는 스케줄러가 작업 실행 순서를 지정할 수 있도록 해 줍니다.
yielding상태인 경우에서도 마찬가지로 상태를 runnable로 변경하고 스케줄러에 작업을 추가합니다.
interrupt, thread.sleep등으로 인해 parked상태가 된다면, unpark상태로 parked를 벗어납니다.
해당 메서드에서는 parked상태인 Virtual Thread 중 unpark가 가능한 Virtual Thread를 찾아 다시 스케줄러에 넣는 모습을 볼 수 있습니다.
정리
이 메서드들 외에도 여러 메서드가 있지만, 오늘은 Virtual Thread의 라이프사이클을 간단하게 이해할 정도로만 살펴봤습니다.
주로 상태의 변화, mount와 unmount가 각각 carrier thread에 virtual thread를 언제 어떻게 할당하는지, park와 unpark를 통해 virtual thread의 runnable이 언제 어떻게 스케줄러에 할당되는지 알아보았습니다.
조금 더 깊게 공부하기 위해서는 JNI, Continuation 클래스, unsafe패키지와 같은 부분이 필요할 것 같습니다.
지금은 공부할 것이 너무 많아서, Virtual Thread 사용시 이슈를 빠르게 발견할 수 있을 정도로만 구조를 파악해 본 글에 정리하게 되었습니다.
수정사항 또는 잘못 서술/이해한 부분이 있다면, 언제나 댓글, 이메일로 의견 남겨주시면 됩니다.
'JAVA > Thread' 카테고리의 다른 글
[Thread] 4. Virtual Thread 알아보기 - 2 (0) | 2024.01.28 |
---|---|
[Thread] 3. Virtual Thread알아보기 (0) | 2023.12.19 |
[Thread] 2. JAVA에서 Thread사용하기 (1) | 2023.12.19 |
[Thread] 1. Process와 Thread의 차이 (0) | 2023.12.18 |