Threads lasten CPU nicht genug aus
-
Hallo!
Ich habe ein Programm, das in verschiedenen Threads Berechnungen anstellt. Das Ganze hat die folgende Struktur:Thread[] threads = new Thread[20]; for (int i = 0; i < 20; ++ i) { thread[i] = new Thread(new asdRunnable()); thread[i].start(); } for (int i = 0; i < 20; ++ i) { try { thread[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } }
Das ist dann noch mal in einer Schleife, es werden also sehr viele Threads erstellt, gestartet und wieder vereinigt.
Ohne Multithreading hat das Programm vorher immer einen Kern der CPU voll ausgelastet. Ich hatte mir erhofft, dass es nun alle auslastet und vier mal so schnell wird. Statt dessen sind aber alle immer nur ein bisschen ausgelastet und die Geschwindigkeit hat sich nicht nennenswert erhöht...
woran kann das liegen?
Braucht das Erstellen und Starten der Threads so viel Zeit?
-
es werden also sehr viele Threads erstellt, gestartet und wieder vereinigt
Das ist schlechte Threadprogrammierung, da Resourcen fuer die Erzeugung und Bereinigung verschwendet werden.
Ich hatte mir erhofft, dass es nun alle auslastet und vier mal so schnell wird.
Einfach alles in Threads packen und hoffen, dass es schneller wird, ist ein Trugschluss. Da muss man wohl etwas mehr Grips aufwenden.
woran kann das liegen?
An vielen Dingen ... habe aber grad keine Glaskugel.
-
Das ist schlechte Threadprogrammierung, da Resourcen fuer die Erzeugung und Bereinigung verschwendet werden.
Ich lese gerade das Buch "Java Threads" von Scott Oaks und habe dort gefunden:
If you're writing a program and it is easier to abandon a thread and create a new one rather than reusing an existing one, in most cases that's what you should do.
Ich habe das Programm jetzt trotzdem so umgestellt, dass immer nur genau vier Threads erstellt werden. Diese enthalten jeweils ein Runnable, das nach jedem Durchlauf mit neuen Werten versorgt wird. Dann werden die Threads wieder gestartet, ohne neu erzeugt zu werden. Trotzdem hat sich die Laufzeit nicht verbessert. Ich habe das Gefühl, alle vier Threads laufen auf dem gleichen CPU-Kern... kann das überhaupt passieren?
EDIT: Die Verteilung wird vom Betriebssystem geregelt, so dass man da nichts dran ändern kann, oder?
-
Und wie lange braucht ein Thread bis er fertig ist? (auch im Vergleich zur Threaderzeugung) Muessen die einzelnen Threads sich synchronisieren? Sind die Laufzeiten der Threadfunktionen etwa gleich lang?
Es sind einfach zu viele Unbekannte, um deine Frage zu beantworten. Leider laesst du auch keine Informationen rueberwachsen. Eine Glaskugel besitze ich nicht ...
Ich habe das Gefühl, alle vier Threads laufen auf dem gleichen CPU-Kern
Gefuehle sind ein schlechter Indikator.
-
Also ich habe früher auch mal bei einem Projekt gedacht, dass ich Threads einfach wieder schließe und neu erzeuge, wie ich lustig bin. Dabei wurden in sehr schneller Abfolge (und in einer zeitkritischen Routine) Threads gestartet. Die angestrebte Geschwindigkeitssteigerung wurde natürlich total umgekehrt, genaue Zahlen kann ich da nicht mehr nennen. Tatsache ist, dass es bei zeitkritischen Angelegenheiten und zeitlich kurzen Threads eigentlich immer deutlich besser ist, den Thread laufen zu lassen und nach und nach mit Arbeit zu füttern. Die Aussage deines Buches ist so einfach nicht richtig.
-
Ich starte die vier Threads. Jeder braucht für die Berechnung etwa eine halbe Sekunde (alle etwa gleich lang, zwischendrin wird ein Zugriff auf eine gemeinsame Variable mit synchronized(this) gemacht). Im Hauptthread, von dem aus ich die anderen vier starte, lasse ich in der Zeit eine Schleife laufen, die auf die Fertigstellung der vier Threads wartet:
for (int threadIndex = 0; threadIndex < 4; ++ threadIndex) try { if (thread[threadIndex] != null) thread[threadIndex].join(); } catch (InterruptedException e) { }
Wenn ich das Programm starte, zeigt xosview 100% Belastung bei einem der vier Kerne an, alle paar Sekunden wechselt allerdings der benutzte Kern. Die anderen drei Kerne sind überhaupt nicht ausgelastet.
Auf einem etwas neueren Windows-Rechner (Dualcore) sind beide Kerne etwa zu 50% ausgelastet.
-
Kleiner TIP: Wie wäre es nur soviele Threads zu starten wie es CPUs (Kerne gibt) ?
Mit 20 Threads ist der Scheduler mehr damit beschäftigt den Kontext etc zu wechseln als sonstwas.
-
snOOfy schrieb:
Ich starte die vier Threads. Jeder braucht für die Berechnung etwa eine halbe Sekunde (alle etwa gleich lang, zwischendrin wird ein Zugriff auf eine gemeinsame Variable mit synchronized(this) gemacht).
Da du nicht viel sagst, kann mann nur raten. Ich sag mal synchronized(this) ist das Problem.
-
Thread.join ist Murks. Es ist erstmal richtig, die Berechnungs-Threads nicht ständig neu zu erzeugen, sondern nach Möglichkeit immer wieder weitere Aufgaben erledigen zu lassen. Threads zu erzeugen und zu starten ist sehr teuer. Deine "Freunde" bei Multithreaded-Programmen sind synchronized, Object.wait und Object.notify bzw. Object.notifyAll. Damit lässt sich alles an Synchronisierung realisieren was man so braucht.
Ich weiß zwar nicht was dein Programm machen soll bzw. wie sich die Berechnungen parallelisieren lassen, aber so wie es sich anhört ist möglicherweise folgender grundsätzlicher Aufbau sinnvoll:
WorkerThreads bzw. deren Runnables:
run() { while( (job = wl.getNextJob()) != null ) job.execute(); }
Dazu eine eigene spezielle Job Klasse für die Berechnungen:
class Job { Job(/*Daten für Berechnung*/){ } execute() { // aufwendige Berechung hier erledigen... wl.submitResult( /* Ergebnisse */ ); } }
und dann noch eine zentrale Klasse fürs komplette Arbeitspensum:
class Workload { synchonized Job getNextJob() { if( isMoreWorkToDo... ) return new Job( /* Daten für die nächste Teilaufgabe */ ); return null; } synchonized submitResult(/* Ergebnisse */) { // Ergebnisse zusammenführen if( isFinished() ) notify(); } boolean isFinished() { return isWorkComplete...; } }
Und im MainThread:
Workload wl = new Workload(); for(0..ThreadCount) new WorkerThread(wl).start(); synchronized(wl) { if(!wl.isFinished()) wl.wait(); } // das komplette Arbeitspensum ist jetzt abgearbeitet
Da fehlen jetzt natürlich noch ein paar try/catches und diverser anderer Kram, aber das Prinzip sollte ersichtlich sein: Die Workload-Klasse kapselt die Gesamtaufgabe, also das komplette Arbeitspensum, das es zu parallelisieren gibt und an dem die Threads zusammen eine Weile zu kauen haben werden. Solange getNextJob() neue Teilaufgaben (Jobs) liefert, laufen die Threads und arbeiten alles ab was sie bekommen können. Danach laufen sie aus und verschwinden ohne dass man sich weiter drum kümmern muss. Innerhalb der beiden Methoden von WorkLoad passiert alles Single-Threaded (da jeweils synchronized), es kann also problemlos an zentralen Datenstrukturen manipuliert werden. Diese Methoden sollten aber selbst möglichst wenig berechnen und zügig wieder beendet werden, damit die Threads wieder weiterlaufen können.
Die erzeugten Job-Objekte sollten alle Daten er- bzw. enthalten, die sie zur Berechnung einer Teilaufgabe benötigen, so dass sie während der Berechnung möglichst keine externen Datenstrukturen verändern müssen. Wenn sie mit einer Teilaufgabe fertig sind liefern sie das Ergebnis der Berechnung wieder bei der Workload-Instanz ab, die die Ergebinsse zusammenführt und, wenn alles fertig ist, mit notify() dem wartenden Main-Thared signalisiert, dass er die fertigen Ergebnisse weiterverarbeiten kann.
Der Mainthread kann natürlich statt die ganze Zeit nur zu warten auch zwischendurch immer mal wieder einen Progressbar aktualisieren:for(int percentComplete=0;true;) { gui.updateProgress(percentComplete); // außerhalb des synchronized-Blocks, um die Worker-Threads nicht zu blockieren synchronized(wl) { if( wl.isFinished() ) break; wl.wait(500); percentComplete = wl.getPercentCompleted(); } }
-
Besorg Dir ein Buch, das nicht hoffnungslos veraltet ist und benutze die Java Concurrency API.