Giter Site home page Giter Site logo

java-example's People

Contributors

iamminji avatar

Watchers

 avatar  avatar

java-example's Issues

Virtual Thread 톺아보기

⚠️ 내용에 오류가 있을 수 있습니다. 암튼 난 책임이 없다 어쩌고

Virtual Thread

Java 19에서 등장한 Virtual Thread 에 대해 알아본다.

들어가기에 앞서

Process 와 Thread 의 차이

인터뷰 단골 문제로 유명함

  • 하나의 프로세스 안에 여러 쓰레드가 돌아간다
  • 프로세스간의 Context swich 비용이 (쓰레드 보다) 더 많이 든다. (오버헤드가 더 큼)

쓰레드간에도 Context switch 발생하지만 PCB 를 쓰는 개념이 아님 (TCB라고 있음)

무슨 말인가 하면 프로세스간의 Context switching 은 Process control block 에 process 의 상태 정보를 saveing, restoring 하고 (프로세스간의 전환으로 메모리나 주소 공간까지 스위칭 하는 것임)

쓰레드간의 Context switching 은 (이와 비슷하지만) 프로세스의 상태 정보가 아닌 program counter, register, stack pointer 등만을 전환한다. 그래서 상대적 비용이 프로세스간의 Context switching 보다 저렴하다고 하는 것임

Process = [Code + Data + Heap + Stack + PC + PCB + CPU Registers]
Thread = [Parent Code, Data and Heap + Stack, PC and CPU Registers]

What is Thread?

쓰레드라는 용어가 붙은 개념(??) 이 생각보다 많음

  • Kernel thread
  • Green thread
  • User thread
  • Native thread (Platform thread)

각각이 개별적인 거라기 보다는 서로 포함되거나, 연관이 있다.

❗ 여기서 잠깐 ❗ 컴퓨터 CPU 의 쓰레드는 그럼 어떤 쓰레드일까요?

흔히 말하는 CPU 쓰레드는 여기서 주로 이야기하는 쓰레드와는 의미가 다르다. 이는 보통 (인텔 기준) hyperthread 라고 한다.

물리적 코어를 가상 코어로 나누어 성능의 이점을 얻는 기술이다.

그래서 소프트웨어 개발에서 말하는 쓰레드와는 그 의미가 다르다.

암튼 보통 CPU 가 1 코어에 2 쓰레드인데 SW 개발 시에는 2개 이상도 띄울 수 있는 이유는 하드웨어 쓰레드와 그 의미가 다르다고 이해하면 좋을 것 같다. (단, 띄우고 실행하는 데는 문제가 없지만 한 번에 행할 수 있는 수행 단위는 분명 CPU 영향을 받는다. 예를 들면 I/O 작업 같은 경우..?)

Kernel Thread 와 User Thread

커널 쓰레드와 유저 쓰레드 간 모델

image
A kernel thread is a schedulable entity, meaning the system scheduler handles the kernel thread.

These threads, known by the system scheduler, are strongly implementation-dependent. To facilitate the writing of portable programs, libraries provide user threads.

kernel thread

A kernel thread runs within a process, but can be referenced by any other thread in the system. The programmer has no direct control over these thread unless you are writing kernel extensions of device drivers. (kthread)

  • context switching 시간이 길다. (interrupt 때문)
  • blocking system call 수행 시 커널이 다른 쓰레드를 실행시킬 수 있다.
  • fair scheduling

user thread

A user thread is an entity used by programmers to handle multiple flows of controls within a program. The API for handling user threads is provided by the threads library. A user thread only exists within a process; a user thread in process A cannot reference a user thread in process B. (pthread)

  • 싱글 쓰레드인 커널에서 유저 레벨 쓰레드가 blocking system call 하면 다른 모든 쓰레드들은 system call 이 완료될 때 까지 대기해야 한다.
  • unfair scheduling

kernel space (또는 커널 모드에서 생성된) 의 쓰레드는 kernel thread, user space 의 쓰레드는 user thread 인 것임

❗ 여기서 잠깐 ❗ 어떤 쓰레드가 더 빠를까요?

쓰레드 모델에 따라 다르긴 하지만.. 단순히 생각해보면

User Level Thread 가 Kernel Level Thread 보다 빠르다. one-to-many 모델을 생각했을 때 컨텍스트 스위칭 비용이 user level thread 보다 kernel level thread 가 크기 때문이다.

자바 쓰레드

Java is made of threads

자바 자체가 쓰레드로 만들어짐

  • Exceptions
  • Thread local
  • Debugger
  • Profiler

자바의 쓰레드는 JVM 에 의해 관리 된다.

  1. JVM 쓰레드와 커널 쓰레드간의 관계는 Green thread 는 M:1
  2. Platform thread(Native thread 이며 우리가 흔히 말하는 자바의 그 쓰레드다.) 는 1:1
  3. virtual thread 는 M:N

즉 JVM 쓰레드는 내부적으로 커널 쓰레드를 사용하고 우리가 Thread 로 생성하는 쓰레드는 User thread 가 되는 것이다. (JVM 구현체마다 조금씩 다르다곤 하지만....)

Green thread

자바에는 원래 green thread 라는 개념이 있었다. (1.1에서 등장했고 1.3에서 버려졌다. Native thread 로 감)

왜 버렸나?

green thread 는 JVM : Kerenl = M : 1 인 모델이기 때문에 CPU 코어를 하나밖에 사용할 수 없다. 그래서 멀티코어일 경우 성능상의 이점을 얻을 수 없다. (근데 이건 반박글이 좀 있어서..... 할 수 있는 구현체가 있다고도 함)

not achieve parallelism performance gains like operating system threads

Green thread ...

  • is platform dependent
  • can run only on one CPU
  • can't adjust scheduling among all the threads

Platform Thread

(우리가 흔히 아는 바로 그 쓰레드다.) Platform 쓰레드는 OS 레벨 쓰레드(커널 레벨)의 단순 wrapper 다. Green thread 와 달리 JVM 과 커널 쓰레드가 1:1 이다. (each Java platform thread is associated 1-to-1 with an OS kernel thread)

성능 향상을 위해 머신에 비용을 쓰고 싶지 않으면 Asynchronous 한 프로그래밍을 하면 되긴 하지만.... loses observability

1:1 관계이기 떄문에, 사용된 유일한 JVM 스레드는 하나의 OS 쓰레드를 사용하고 하나의 OS 쓰레드는 하나의 CPU 코어만 쓰게 된다. 그러면 나머지 코어는 놀게 되어버린다..!

또한 어플리케이션의 성능을 향상 시키기 위해 쓰레드를 추가하면 context switching 에 많은 비용이 들어가게 된다. (쓰레드 풀을 사용해도 결국엔 CPU, 네트워크, 메모리에 있어서 보틀넥이 생길 수 밖에 없음)

웹 어플리케이션에서는 요청 당 하나의 쓰레드를 생성하는 Thread-per-request model 을 채택하고 있다. 이는 blocking 이 완료될 때 까지 쓰레드를 재할당하지 못한다는 것이고, platform thread 방식은 커널 쓰레드와 1:1 이기 때문에 비용 문제가 있다(스케줄링 / context switching 발생). 자바 프로그래밍에서는 이 문제 때문에 보통 스레드 풀을 만들어 사용한다.

자바에서는 그래서 리액티브 프로그래밍 방식 (RxJava, reactor, WebFlux...) 이 등장하였지만 비동기 프로그래밍은 너무 어려웠다! 그래서 Virtual thread 가 등장하게 된다.

Platform thread 의 특징

  • java.lang.Thread (Platform threads)
  • One implementation: OS threads
  • OS threads support all languages
  • RAM-heavy - megabyte-scale;page granularity; can't uncommit
  • Task-switching requires a switch to kernel
  • Scheduling is a compromise for all usages. Bad cache locality
❗ 여기서 잠깐 ❗ 서버의 최대 유저 레벨 쓰레드 개수 이상만큼 어플리케이션 쓰레드를 띄울 수 없는걸까요?

리눅스에는 maximum thread 개수를 볼 수 있는 명령어가 있다. (커널 파라미터가 존재하기 때문에 설정도 할 수 있음)

$ cat /proc/sys/kernel/threads-max

위 명령어는 개별 프로세스가 아닌, 리눅스에서 사용할 수 있는 전체 프로세스의 수를 의미한다. 이 프로세스의 수는 사실 count 의 개념이 아니라 가상 메모리의 사이즈에 좌우 되는 값이다.

number of threads = total virtual memory / (stack size*1024*1024)

따라서 이 값을 올리려면 가상 메모리 값을 올리면 된다.

즉, 커널 파라미터로 나오는 쓰레드 값 보다 더 많은 개수의 프로세스(쓰레드) 개수를 띄울 수 있고 저 값은 메모리라서 너무 많아지면 OOM 이 날 ...것 이다. 아마도?

Virtual Thread 란?

Virtual Thread 탄생 배경

The idea is to build support for light-weight threads on top of the JVM threads and fundamentally decouple the JVM threads from the native kernel threads.

정리하자면 Virtual Thread 는 다음의 이유로 시작된 프로젝트다.

  • Blocking a platform thread is expensive
  • Reactive code 를 쓰지만... 근데 너무 어렵다! (읽기/쓰기/테스트... 😢)

참고로 이전에는 이 프로젝트? 이 기능? 을 fiber 라고 불렀다. fiber 로 되어 있다면 이 Virtual thread 의 초기 모델이라고 보면 된다.

Project loom 에 의해서 시작된 프로젝트이고 자바 19에서 preview 로 들어갔다.

project loom 에는 virtual thread 뿐만 아니라 delimited continuation, and tail-call elimination 도 포함된다.

How virtual threads work

Virtual threads introduce an abstraction layer between operating-ssytem processes and application-level concurrency. Said differently, virtual threads can. be used to schedule tasks that the JVM oerchestrates, so the JVM mediates between the operating system and the program

image

기존 쓰레드가 OS 에 의해 매핑되고 관리되었다면, Virtual thread 에서는 application 은 virtual thread를 생성하고 JVM 이 이를 처리할 컴퓨팅 리소스를 할당한다.

Golang 의 고루틴이랑 비슷하다고 하네요.

일종의 lightweight thread 이다.
(Virtual thread 는 M:N 이라 적은 OS Thread 를 쓸 수 있음)

  • java.lang.Thread (API 비슷)
  • The Java runtime is well positioned to implement treads
  • Resizable stacks (possible b/c we only need to support Java)
  • Context-switching in user-mode
  • Work-stealing scheduler optimised for transaction processing

VIrtual thread 의 특징

  • It is a Thread in code, runtime, debugger, and profiler
  • It's a Java entity and not a wrapper around an OS thread
  • Creating and blocking them are cheap operations
  • They should not be pooled
❓ 엥? pool 을 안쓴다고?

생성과 폐기 비용이 저렴해서 pool 을 사용할 필요가 없다! (JVM 이 알아서 쓰레드 풀을 관리 해준다.) 그래서 ExecutorService 에서 앞으로 쓰레드를 만들 때 요렇게 만들어주면 된다.

참고로 preview 기능이다.

ExecutorService executor1 = Executors.newVirtualThreadPerTaskExecutor(); // New method
ExecutorService executor2 = Executors.newFixedThreadPool(30); // Old method

// 혹은 요렇게 factory 를 넘겨서 만들 수도 있다.
Executors.newThreadPerTaskExecutor(ThreadFactory threadFactory)

// ThreadFactory 는 요렇게도 얻을 수 있다.
Thread.ofVirtual().factory()
  • Virtual threads use a work-stealing ForkJoinPool scheduler

carrier 라는 쓰레드가 Virtual thread 를 만든다.

  • Pluggable schedulers can be used for asynchronous programming
  • A virtual thread will have its own stack memory
  • The virtual threads API is very similar to platform threads and hence easier to adopt/migration

Virtual Thread 뭐가 좋은건데?

  • Virtual threads are cheap about 1000 times cheaper than classical platform threads.
  • blocking a virtual thread is also cheap, so trying to avoid blocking a virtual thread is useless. Writing classical blocking code is OK. (blocking code is so much easier to write than asynchronous code)

Virtual Thread 예시

vThreads 의 flag 를 변경시켜가면서 실행해본다.

import java.util.Random;

public class App {

    public static void main(String[] args) {
        boolean vThreads = true;
        System.out.println("Using vThreads: " + vThreads);

        long start = System.currentTimeMillis();

        Random random = new Random();
        Runnable runnable = () -> {
            double i = random.nextDouble(1000) % random.nextDouble(1000);
        };
        for (int i = 0; i < 50000; i++) {
            if (vThreads) {
                // virtual thread 로 실행하기
                Thread.startVirtualThread(runnable);
            } else {
                // paltform thread 로 실행하기
                Thread t = new Thread(runnable);
                t.start();
            }
        }

        long finish = System.currentTimeMillis();
        long timeElapsed = finish - start;
        System.out.println("Run time: " + timeElapsed);
    }
}

또는 Thread 클래스에서 새로 생긴 ThreadFactory builder 로 아래 처럼 실행시킬 수도 있다.

public class ThreadExample {

    public static void main(String[] args) throws InterruptedException {

        // Thread[#22,Thread-0,5,main]
        var pThread = Thread.ofPlatform()
            .unstarted(() -> System.out.println(Thread.currentThread()));

        pThread.start();
        pThread.join();

        // VirtualThread[#23]/runnable@ForkJoinPool-1-worker-1
        var vThread = Thread.ofVirtual()
            .unstarted(() -> System.out.println(Thread.currentThread()));

        vThread.start();
        vThread.join();

        // class java.lang.VirtualThread
        System.out.println(vThread.getClass().getName());
    }
}

Virtual Thread로 가보자고

  • virtual thread can run any Java code or any native
  • do not need to learn any new concepts / 코드가 기존 쓰레드랑 같음 (Simple, blocking I/O api). but you need to unlearn certain ideas.

Virtual Thread 무조건 좋은걸까?

  • 데이터스토어와 같은 리소스에 액세스할 때 제한이 있는 쓰레드를 사용하는 대신(pooling 을 쓰는 대신) 세마포어(semaphores)를 사용해서 쓰레드의 수를 제어하는 편이 낫다.
  • 같은 개수의 platform thread 에서 virtual thread 로 가는건 benefit 이 없음 (100개의 platform thread 에서 100개의 Virtual thread로 갈 필요가 없음 다른게 없기 때문임)

(TODO 쓰레드 테스트에서 실행 속도의 이점을 보았는데 왜 benefit 이 없다고 하는걸까?)

  • 대신 100개의 쓰레드 풀에서 100000개의 unpooled 쓰레드로 가는건? OK (shared thread pools / few threads 에서 new thread per task / lots of threads) Never pool virtual thread! (use semaphores to limit concurrency)
  • Don't cache expensive objects in ThreadLocals (그냥 싱글 인스턴스를 쓰라고 함. 비용 문제 때문인듯 Virtual thread 자체가 리소스라서)

참고

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.