• 코드를 블로킹 IO로 작성했는데도, 입출력 동안 스레드가 대기하지 않고 다른일을 시키면 얼마나 좋을까!
    • 이게 가능하면
    • CPU 유휴 시간 줄음.
    • 더 많은 작업 처리 가능.
    • 개발자는 성능 향상위해 별도의 기술 안써도 됨.
  • 자바의 가상 스레드나 Go의 고루틴을 사용하면 됨!
  • 언어는 다르지만 가상 스레드와 고루틴은 경량 스레드라는 공통점 가짐.

경량 스레드란, 경량 스레드란 OS가 관리하는 스레드가 아닌 JVM 같은 언어의 런타임이 관리하는 스레드

OS가 CPU로 실행할 스레드를 스케줄링 하듯, 언어 런타임이 OS 스레드로 실행할 경량 스레드를 스케줄링함.

자바의 경우 JVM이 스케줄러 풀 가지고있는데 여기 드가는 스레드 이름이 ‘플랫폼 스레드’고 이건 OS 스레드와 1대1매핑됨

JVM 경우 기본적으로 풀에 CPU 코어 개수만큼 플랫폼 스레드 생성하고 필요에 따라 플랫폼 스레드 증가시킴.

이 책의 시점에서 풀에 최대로 생성가능한 플랫폼 스레드의 기본값은 256

가상 스레드를 왜 경량 스레드라 하나

플랫폼 스레드보다 더 적은 자원 사용하기 때문.

가상 스레드는 플랫폼 스레드(즉 OS 스레드)보다 적은 메모리 사용.

1만개의 플랫폼 스레드 생성해야한다고 가정해보자. 스레드의 기본 스택 크기가 1MB일 때 10,000MB(약 9.8GB)의 메모리 사용. 물론 이는 예약(reserved)한 메모리 기준이며 실 사용하는(committed) 메모리는 이보다 작다. 하지만 예약한 메모리라 해도 상당량이 필요한거 알 수 있다.

가상 스레드는 훨씬 적은 메모리 씀. 가상 스레드 1개가 평균적으로 2KB 메모리 쓴다고 했을때 1만 개 가상 스레드가 사용하는 힙 메모리는 약 20BM정도. 여기에 스케줄링 위한 플랫폼 스레드가 8개라고 하면 추가로 8MB의 스택 메모리 사용. 즉 1만개의 가상 스레드 실행위해 20+8 = 28MB 메모리 씀. 300배 넘게 차이!

가상 스레드도 힙 메모리 영역을 쓴다.

두 개 의 생성 시간도 차이가 큼. 책 기준 10만개 플랫폼, 가상 생성 차이가

  • 플랫폼 스레드: 21,467ms
  • 가상 스레드: 196ms

이는 톰캣처럼 요청별 스레드 생성하는 서버에서 가상 스레드 쓰면 더 적은 메모리로 더 많은 요청 처리할 수 있다는 뜻

캐리어 스레드

가상 스레드를 실행하는 플랫폼 스레드를 캐리어 스레드라고도 표현. CPU가 여러 스레드 실행하는 것처럼, 한 개의 캐리어 스레드도 여러 가상 스레드를 실행. 특정 가상 스레드가 특정 캐리어 스레드에 연결되어 있는것을 마운트 되었다 표현.

반대로 가상 스레드가 캐리어 스레드로부터 언마운트 되면 가상 스레드는 멈춘다.

네트워크 IO와 가상 스레드⭐️

가상 스레드는 실행하는 과정에서 블로킹되면 플랫폼 스레드와 언마운트되고 실행이 멈춤. 언마운트된 플랫폼 스레드는 실행 대기 중인 다른 가상 스레드와 마운트하고 실행 재게.

블로킹 연산과 synchronized

블로킹 연산에는 IO, ReentrantLock, Thread.sleep()등이 포함. 이걸로 가상 스레드가 블로킹되면, 플랫폼 스레드는 대기 중인 다른 가상 스레드 실행. 반면 자바 23 또는 이전 버전에서 synchronized로 블락되면, 가상 스레드는 언마운트 안됨. 즉, 플랫폼 스레드고 같이 블로킹이 됨!!! 이렇게 같이 블로킹 된것을 가상 스레드가 플랫폼 스레드에 고정됐다라함(pinned)

자바21기준으로 synchronized 외에도 JNI 호출 등 가상스레드가 플랫폼 스레드에 고정되는 경우가 있는데 이렇게 고정되면 CPU 효율 높일 수 없음. 이를 유의하자.

가상 스레드와 성능

가상 스레드는 IO 중심 작업일 때 효과 있다. IO는 가상 스레드가 지원하는 블로킹 연산이므로, IO 중심 작업일 때 플랫폼 스레드가 CPU 낭비 없이 효율적으로 여러 가상 스레드를 실행할 수 있다.

그런데 IO 중심 작업이라고 무조건 가상 스레드가 이점이 있는게 아님. 스케줄링에 사용되는 플랫폼 스레드 개수보다 가상 스레드 개수가 많아야 효과 기대 가능. 예를들어 다음과 같은 상황일때

  • 장비 CPU 코어는 16개
  • 서버의 평소 TPS 500
  • 1개 요청 처리 시간은 20ms
  • 모든 요청은 IO 중심 작업

단순히 계산해보면 1개의 스레드는 1초 동안 약 50개 요청 처리 가능하니 1초에 500개 요청 처리하려면 10개의 스레드 필요. 즉, 동시에 10개 요청을 처리할수 있으면 됨.

이 서버에서 가상 스레드 쓰면 다음과 같이 오히려 플랫폼 스레드가 더 많이 생기는 상황 발생

  • 플랫폼 스레드는 기본으로 16개 생김(CPU core 가16 개)
  • 동시 요청은 10개 이므로 동시에 생성되는 가상 스레드는 10개 동시에 10개의 가상 스레드 실행되지만 IO 중심이라 플랫폼 스레드는 실행 가능한 다른 가상 스레드를 찾음. 그런데 죄다 IO 대기중이라 실행할게 없는 것. 이러면 가상 스레드 이점이 없다. 가상 스레드는 플랫폼 스레드보다 개수가 많을때 효과가 있는 것

그러면 이 상황에서 가상 스레드 이점 얻으려면 CPU 코어 줄이거나 트래픽이 많아져야됨. 클라우드 환경이라면 CPU 코어를 16개에서 4개로 줄이고 메모리도 더 줄여서 더 적은 비용으로 같은 트래픽 처리 가능! 동시에 필요한 스레드가 100개에서 1000개이상이 될정도로 트래픽 증가해도 가상 스레드 이점 얻기 가능. 같은 CPU, Memory로 처리량을 10배 늘리는 것!!!

가상 스레드로 높일 수 있는 것은 처리량이다. 실행 속도가 플랫폼 스레드보다 빠를 순 없다(당연하지). 결국 실행시키는건 동일한 CPU니깐.

가상 스레드는 플랫폼 스레드보다 생성 비용이 적어서 스레드 풀 미리 구성할 필요도 없다고하네 ㅎㄷㄷ

가상 스레드 중요 장점

기존 코드를 크게 수정할 필요가 없는 것. 스프링 프레임웍이나 MySQL JDBC 드라이버등 유명한 라이브러리, 프레임워크들은 이미 가상 스레드 지웜. 조금만 신경쓰면 기존 코드 유지하면서도 가상 스레드로 서버 성능 향상이 가능.