webentwicklung-frage-antwort-db.com.de

Warum soll das Erstellen eines Threads teuer sein?

In den Java Tutorials heißt es, dass das Erstellen eines Threads teuer ist. Aber warum ist es genau so teuer? Was passiert genau, wenn ein Java= Thread erstellt wird, der erstellt wird Ich nehme die Aussage als wahr, interessiere mich aber nur für die Mechanismen der Thread-Erstellung in JVM.

Thread-Lebenszyklus-Overhead. Thread-Erstellung und Teardown sind nicht kostenlos. Der tatsächliche Overhead variiert je nach Plattform, aber die Thread-Erstellung benötigt Zeit, was zu einer Verzögerung bei der Anforderungsverarbeitung führt und einige Verarbeitungsaktivitäten durch die JVM und das Betriebssystem erfordert. Wenn Anforderungen häufig und einfach sind, wie in den meisten Serveranwendungen, kann das Erstellen eines neuen Threads für jede Anforderung erhebliche Rechenressourcen beanspruchen.

From Java Concurrency in der Praxis
Von Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes und Doug Lea
Print ISBN-10: 0-321-34960-1

167
kachanov

Die Erstellung von Java-Threads ist teuer, da viel Arbeit anfällt:

  • Für den Thread-Stack muss ein großer Speicherblock reserviert und initialisiert werden.
  • Systemaufrufe müssen durchgeführt werden, um den nativen Thread im Host-Betriebssystem zu erstellen/zu registrieren.
  • Deskriptoren müssen erstellt, initialisiert und zu internen JVM-Datenstrukturen hinzugefügt werden.

Es ist auch in dem Sinne teuer, dass der Thread Ressourcen bindet, solange er lebt; z.B. Der Thread-Stapel, alle vom Stapel aus erreichbaren Objekte, die JVM-Thread-Deskriptoren und die systemeigenen Thread-Deskriptoren.

Die Kosten für all diese Dinge sind plattformspezifisch, aber auf keiner Java Plattform, auf die ich jemals gestoßen bin, sind sie günstig.


Bei einer Google-Suche wurde ein alter Benchmark gefunden, der eine Thread-Erstellungsrate von ~ 4000 pro Sekunde auf einem Sun Java 1.4.1 auf einem Xeon mit zwei Prozessoren aus dem Jahr 2002 und einem Linux aus dem Jahr 2002 angibt. Eine modernere Plattform wird bessere Zahlen liefern ... und ich kann die Methodik nicht kommentieren ... aber zumindest gibt es einen Standard für wie teuer die Erstellung von Threads wird wahrscheinlich sein.

Das Benchmarking von Peter Lawrey zeigt, dass die Thread-Erstellung heutzutage in absoluten Zahlen erheblich schneller ist, es ist jedoch unklar, inwieweit dies auf Verbesserungen von Java und/oder des Betriebssystems zurückzuführen ist ... oder auf schnellere Prozessorgeschwindigkeiten. Aber seine Zahlen immernoch bedeuten eine 150-fache Verbesserung, wenn Sie einen Thread-Pool verwenden, anstatt jedes Mal einen neuen Thread zu erstellen/zu starten. (Und er macht den Punkt, dass dies alles relativ ist ...)


(Das oben Gesagte geht eher von "nativen Threads" als von "grünen Threads" aus. Moderne JVMs verwenden jedoch aus Leistungsgründen alle native Threads. Das Erstellen von grünen Threads ist möglicherweise billiger, aber Sie zahlen dafür in anderen Bereichen.)


Ich habe ein bisschen gegraben, um zu sehen, wie der Stapel eines Java -Threads wirklich zugewiesen wird. Im Fall von OpenJDK 6 unter Linux wird der Thread-Stapel durch den Aufruf von pthread_create Zugewiesen, der den nativen Thread erstellt. (Die JVM übergibt pthread_create Keinen vorab zugewiesenen Stapel.)

Dann wird der Stack innerhalb von pthread_create Durch einen Aufruf von mmap wie folgt zugewiesen:

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

Gemäß man mmap Bewirkt das Flag MAP_ANONYMOUS, Dass der Speicher auf Null initialisiert wird.

Obwohl es möglicherweise nicht unbedingt erforderlich ist, dass neue Java - Threadstapel (gemäß der JVM-Spezifikation) auf Null gesetzt werden, werden sie in der Praxis (zumindest mit OpenJDK 6 unter Linux) auf Null gesetzt.

142
Stephen C

Andere haben diskutiert, woher die Kosten für das Einfädeln stammen. Diese Antwort deckt ab, warum das Erstellen eines Threads im Vergleich zu vielen Vorgängen nicht so teuer ist, aber relativ teuer im Vergleich zu Aufgabenausführungsalternativen, die relativ weniger teuer sind.

Die naheliegendste Alternative zum Ausführen einer Aufgabe in einem anderen Thread besteht darin, die Aufgabe in demselben Thread auszuführen. Dies ist schwer zu verstehen, wenn man annimmt, dass mehr Threads immer besser sind. Die Logik ist, dass es schneller sein kann, die Aufgabe in dem aktuellen Thread auszuführen, wenn der Aufwand zum Hinzufügen der Aufgabe zu einem anderen Thread größer ist als die Zeit, die Sie sparen.

Eine andere Alternative ist die Verwendung eines Thread-Pools. Ein Thread-Pool kann aus zwei Gründen effizienter sein. 1) Bereits erstellte Threads werden wiederverwendet. 2) Sie können die Anzahl der Threads optimieren/steuern, um eine optimale Leistung sicherzustellen.

Das folgende Programm wird gedruckt ....

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

Dies ist ein Test für eine einfache Aufgabe, der den Overhead jeder Threading-Option aufdeckt. (Diese Testaufgabe ist die Art von Aufgabe, die im aktuellen Thread am besten ausgeführt wird.)

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);
    }
}
}

Wie Sie sehen, kostet das Erstellen eines neuen Threads nur ca. 70 µs. Dies könnte in vielen, wenn nicht den meisten Anwendungsfällen als trivial angesehen werden. Relativ gesehen ist es teurer als die Alternativen und für einige Situationen ist ein Thread-Pool oder gar keine Verwendung von Threads eine bessere Lösung.

71
Peter Lawrey

Theoretisch hängt dies von der JVM ab. In der Praxis hat jeder Thread eine relativ große Menge an Stapelspeicher (standardmäßig 256 KB, denke ich). Außerdem werden Threads als Betriebssystem-Threads implementiert, sodass das Erstellen von Threads einen Betriebssystemaufruf, d. H. Einen Kontextwechsel, umfasst.

Stellen Sie fest, dass "teuer" in der Datenverarbeitung immer sehr relativ ist. Die Thread-Erstellung ist im Vergleich zur Erstellung der meisten Objekte sehr teuer, im Vergleich zu einer zufälligen Festplattensuche jedoch nicht sehr teuer. Sie müssen das Erstellen von Threads nicht um jeden Preis vermeiden, aber das Erstellen von Hunderten von Threads pro Sekunde ist kein kluger Schachzug. Wenn Ihr Entwurf viele Threads erfordert, sollten Sie in den meisten Fällen einen Threadpool mit begrenzter Größe verwenden.

29

Es gibt zwei Arten von Threads:

  1. Proper threads: Dies sind Abstraktionen rund um die Threading-Funktionen des zugrunde liegenden Betriebssystems. Die Erstellung von Threads ist daher so teuer wie die des Systems - es gibt immer einen Overhead.

  2. "Grüne" Threads: Erstellt und geplant von der JVM, diese sind billiger, aber es kommt zu keinem richtigen Paralellismus. Diese verhalten sich wie Threads, werden jedoch im JVM-Thread des Betriebssystems ausgeführt. Sie werden meines Wissens nicht oft verwendet.

Der größte Faktor, an den ich bei der Erstellung von Threads denken kann, ist das Stapelgröße, das Sie für Ihre Threads definiert haben. Die Thread-Stack-Größe kann beim Ausführen der VM als Parameter übergeben werden.

Davon abgesehen ist die Thread-Erstellung hauptsächlich vom Betriebssystem und sogar von der VM-Implementierung abhängig.

Lassen Sie mich jetzt auf Folgendes hinweisen: Das Erstellen von Threads ist teuer, wenn Sie das Auslösen planen 2000 Threads pro Sekunde, jede Sekunde Ihrer Laufzeit. Die JVM ist nicht dafür ausgelegt, dies zu handhaben. Wenn Sie ein paar Stallarbeiter haben, die nicht immer wieder entlassen und getötet werden, entspannen Sie sich.

8
slezica

Das Erstellen von Threads erfordert die Zuweisung einer angemessenen Menge an Speicher, da nicht ein, sondern zwei neue Stapel erstellt werden müssen (einer für Java Code, einer für nativen Code). Verwendung von - Executors /Thread-Pools können den Overhead vermeiden, indem sie Threads für mehrere Aufgaben für Executor wiederverwenden.

6
Philip JF

Offensichtlich ist der Kern der Frage, was "teuer" bedeutet.

Ein Thread muss einen Stapel erstellen und den Stapel basierend auf der Ausführungsmethode initialisieren.

Es muss Kontrollstatusstrukturen einrichten, dh welchen Status es hat, wartet usw.

Es gibt wahrscheinlich eine Menge Synchronisation um diese Dinge einzurichten.

0
MeBigFatGuy