programing

스레드를 만드는 것은 왜 비용이 많이 든다고 하는가?

prostudy 2022. 4. 24. 09:35
반응형

스레드를 만드는 것은 왜 비용이 많이 든다고 하는가?

자바 자습서에서는 스레드를 만드는 것은 비용이 많이 든다고 한다.그런데 정확히 왜 비싸지?자바 스레드가 만들어질 때 그것의 제작 비용을 비싸게 만드는 정확히 무슨 일이 일어나고 있는가?나는 그 진술을 사실로 받아들이고 있지만, JVM의 스레드 생성 역학에 관심이 있을 뿐이다.

스레드 라이프사이클 오버헤드.스레드 생성과 철거는 자유롭지 않다.실제 오버헤드는 플랫폼마다 다르지만, 스레드 생성은 시간이 걸리고, 요청 처리에 지연 시간을 도입하며, JVM과 OS에 의한 일부 처리 활동이 필요하다.대부분의 서버 응용프로그램에서처럼 요청이 빈번하고 경미한 경우, 각 요청에 대한 새 스레드를 만들면 상당한 컴퓨팅 자원을 소모할 수 있다.

실제 Java 동시성으로부터
팀 레이브리안 괴츠, 팀 피얼스, 조슈아 블록, 조셉 보우베어, 데이비드 홈즈, 더그 리아
인데, 0-321-34960-1

스레드를 만드는 것은 왜 비용이 많이 든다고 하는가?

비싸기 때문이다.

Java 나사산 생성은 상당한 양의 작업이 수반되기 때문에 비용이 많이 든다.

  • 스레드 스택에 대해 큰 메모리 블록을 할당하고 초기화해야 한다.
  • 호스트 OS에 네이티브 스레드를 생성/등록하려면 시스템 호출을 수행해야 한다.
  • 설명자는 JVM 내부 데이터 구조에 생성, 초기화 및 추가해야 한다.

또한, 스레드가 살아있는 한, 예를 들어 스레드 스택, 스택에서 도달할 수 있는 모든 개체, JVM 스레드 설명자, OS 네이티브 스레드 설명자 등의 리소스를 묶는다는 점에서 비용이 많이 든다.

이 모든 것의 비용은 플랫폼마다 다르지만, 내가 여태껏 접해 본 어떤 자바 플랫폼에서도 싸지 않다.


구글 검색 결과 2002년 빈티지 리눅스를 실행하는 2002년식 듀얼 프로세서 Xeon의 Sun Java 1.4.1에서 초당 4000개의 스레드 생성률을 보고하는 오래된 벤치마크를 발견했다.좀 더 현대적인 플랫폼은 더 나은 수치를 제공할 것이다. 그리고 나는 방법론에 대해 언급할 수 없다. 그러나 적어도 그것은 실의 제작 비용이 얼마나 많이 들지에 대한 구장을 제공한다.

Peter Lawrey의 벤치마킹은 절대적으로 오늘날 스레드 생성 속도가 현저히 더 빠르다는 것을 보여주지만, 이 중 얼마가 Java 및/또는 OS ... 또는 더 높은 프로세서 속도에서 개선되어야 하는지는 불분명하다.그러나 그의 수치는 여전히 실 풀(srad pool)을 사용할 때 매번 새 실을 만들거나 시작할 때 150배 이상의 향상 효과를 나타낸다. (그리고 그는 이것이 모두 상대적이라는 점을 강조한다.)


위의 내용은 녹색 스레드가 아닌 네이티브 스레드를 가정하지만, 현대의 JVM은 모두 성능상의 이유로 네이티브 스레드를 사용한다.녹색 실은 만드는 것이 더 저렴할 수 있지만, 다른 지역에서는 비용을 지불한다.

업데이트: OpenJDK 롬 프로젝트는 무엇보다도 표준 Java 스레드에 대한 경량 대안 제공을 목표로 한다.는 네이티브 스레드와 녹색 스레드의 하이브리드인 가상 스레드를 제안하고 있다.간단히 말해서, 가상 스레드는 병렬 실행이 필요할 때 아래에 있는 네이티브 스레드를 사용하는 녹색 스레드 구현과 같다.

현재(2021년 1월) 프로젝트 롬 작업은 아직 시제품 제작 단계에 있으며 (AFAIK) 출시를 목표로 한 자바 버전은 없다.


자바 쓰레드의 스택이 어떻게 할당되는지 알아보기 위해 좀 더 자세히 알아봤다., 에서 OpenJDK 6의 경우,, 오다이에의 pthread_create네이티브 실을 만드는 거야(JVM이 통과되지 않음pthread_create사전 할당된 스택)

그러면 안에서.pthread_create로의 호출에 의해 스택이 할당되다.mmap아래와 같이

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

에 따르면man mmapMAP_ANONYMOUS플래그는 메모리가 0으로 초기화되도록 한다.

따라서, 새로운 Java 스레드 스택이 (JVM 사양에 따라) 영점화되는 것이 필수적이지는 않을 수 있지만, 실제로 (최소한 Linux의 OpenJDK 6에서는) 영점화된다.

다른 이들은 나사산 비용이 어디에서 발생하는지에 대해 논의해왔다.이 답은 스레드를 만드는 것이 많은 작업에 비해 비용이 많이 들지 않고 상대적으로 비용이 적게 드는 작업 실행 대안에 비해 상대적으로 비용이 많이 드는 이유를 다룬다.

다른 스레드에서 작업을 실행하는 가장 확실한 대안은 동일한 스레드에서 작업을 실행하는 것이다.이것은 더 많은 실이 항상 더 낫다고 가정하는 사람들에게는 이해하기 어렵다.작업을 다른 스레드에 추가하는 오버헤드가 저장 시간보다 클 경우 현재 스레드에서 작업을 수행하는 것이 더 빠를 수 있다는 논리다.

또 다른 대안은 나사산 풀을 사용하는 것이다.스레드 풀은 두 가지 이유로 더 효율적일 수 있다.1) 이미 만들어진 스레드를 재사용한다.2) 최적의 성능을 얻기 위해 스레드 수를 조정/제어할 수 있다.

다음 프로그램이 인쇄함....

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

이것은 각 스레딩 옵션의 오버헤드를 노출시키는 사소한 작업에 대한 시험이다.(이 테스트 작업은 현재 스레드에서 실제로 가장 잘 수행되는 작업의 일종이다.)

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

보시다시피 새 스레드를 만드는 데 드는 비용은 70µs에 불과하다.이것은 대부분의 사용 사례는 아니더라도 많은 사례에서 사소한 것으로 간주될 수 있다.상대적으로 말해서 그것은 대안보다 더 비싸고 어떤 상황에서는 나사산을 풀거나 아예 사용하지 않는 것이 더 나은 해결책이다.

이론적으로 이것은 JVM에 달려있다.실제로 모든 스레드는 비교적 많은 양의 스택 메모리(디폴트당 256KB)를 가지고 있다.또한 스레드는 OS 스레드로 구현되므로 스레드를 생성하려면 OS 호출, 즉 컨텍스트 스위치가 필요하다.

컴퓨팅의 "비용"은 항상 매우 상대적이라는 것을 깨달으십시오.나사산 생성은 대부분의 물체의 생성에 비해 매우 비싸지만, 무작위 하드디스크 탐색에 비해 그리 비싸지는 않다.어떤 대가를 치르더라도 스레드를 만드는 것을 피할 필요는 없지만, 초당 수백 개의 스레드를 만드는 것은 현명한 행동이 아니다.대부분의 경우, 설계에 많은 스레드가 필요한 경우 제한된 크기의 스레드 풀을 사용하십시오.

실에는 두 가지 종류가 있다.

  1. 적절한 스레드: 기본 운영 체제의 스레드 시설 주변의 추상화.따라서, 나사산 생성은 시스템만큼 비용이 많이 들며, 항상 오버헤드가 존재한다.

  2. "녹색" 스레드: JVM에 의해 만들어지고 스케줄링된, 이것들은 더 저렴하지만, 적절한 마비주의는 일어나지 않는다.이는 스레드처럼 동작하지만 OS의 JVM 스레드 내에서 실행된다.그들은 내가 아는 바로는 자주 사용되지 않는다.

내가 생각할 수 있는 가장 큰 요인은 당신이 당신의 스레드에 대해 정의한 스택 크기 입니다.스레드 스택 크기는 VM을 실행할 때 매개 변수로 전달될 수 있다.

그 외 스레드 생성은 대부분 OS에 의존하며, VM 구현에 의존한다.

한 가지 짚고 넘어가자면, 만약 여러분이 초당 2000개의 스레드를 발사할 계획이라면, 스레드를 만드는 것은 비용이 많이 든다는 겁니다. JVM은 그것을 다루도록 설계되지 않았다.해고당하지 않고 죽임을 당하지 않을 안정된 일꾼 두어 명이 있다면 긴장을 푸십시오.

작성 중Threads하나의 스택이 아니라 두 개의 스택(자바 코드의 경우 하나, 네이티브 코드의 경우 하나)을 새로 만들어야 하기 때문에 상당한 양의 메모리를 할당해야 한다.실행자/스레드 풀을 사용하면 실행자의 여러 태스크에 스레드를 재사용하여 오버헤드를 방지할 수 있다.

분명히 문제의 핵심은 '비용'이 무엇을 의미하느냐 하는 것이다.

스레드는 스택을 만들고 실행 방법에 따라 스택을 초기화해야 한다.

제어 상태 구조, 즉 실행 가능한 상태, 대기 상태 등을 설정할 필요가 있다.

아마 이런 것들을 설치하는데 상당한 동기화가 있을 겁니다.

참조URL: https://stackoverflow.com/questions/5483047/why-is-creating-a-thread-said-to-be-expensive

반응형