Multithreading probleme je nach System

Caste

Grünschnabel
Hallo,

ich bin mit ein paar Studienkollegen dabei ein Spiel in Java zu schreiben mit LWJGL, eigener Physik, Netzwerk...

Wir wollten jetzt den Rendervorgang auslagern in einen eigenen Thread, damit die physik so gut wies geht durchlaufen kann.

Dafür implementiert der Renderer das interface Runnable, und jedesmal wenn die Physik-Engine meint, dass man neuzeichnen müsste (natürlich erst wenn der alte Thread fertig ist) wird über (new Thread(renderer)).start() das ganze aufgerufen.

Komisch ist nun: bei mir (Ubuntu Gutsy 32 bit, Intel Core 2 Duo) läuft die Version mit threads deutlich ruckeliger als wenn man einfach nur renderer.run() aufruft, und netterweise hört es nach einer weile auf zu rendern, die physik läuft weiter. Bei unseren Mac-Nutzern allerdings wird das ganze schneller mit threads und funktioniert wunderbar.

Mache ich da irgendetwas falsch, oder ist Java überfordert mehrere tausende male Threads anzulegen?

Vielen Dank schonmal
Carsten
 
Ich würde dir in jedem Fall empfehlen einen Threadpool zu benutzen, denn die Threads sollen ja immer wieder das gleiche tun und da ist es eigentlich nicht nötig dauernd wieder neue Threads anzulegen.
 
Wozu überhaupt jedesmal einen neuen Thread?
Mehrere Renderthreads bräuchtet ihr nur, wenn das Rendern parallel zu einem anderen Renderer laufen soll. Da du aber schreibst, dass der alte Renderthread natürlich vor dem Starten des neuen beendet wird, ist es eben nicht nötig, dauernd neue Threads zu erzeugen.

D.h. es reicht ein Thread zum rendern, der in einer Endlosschleife läuft und mit wait() wartet, bis neue Daten vorliegen und er mit notify() wieder "aufgeweckt" wird.

Damit läuft der Renderer auf jeden Fall in einem anderen Thread als die Physikengine und es werden nicht unnötig viele neue Threads erstellt.
 
D.h. es reicht ein Thread zum rendern, der in einer Endlosschleife läuft und mit wait() wartet, bis neue Daten vorliegen und er mit notify() wieder "aufgeweckt" wird.

Das hört sich genau nach dem an was ich suche, ich habe jetzt schon einiges damit ausprobiert und meinem Renderer eben die besagte Endlosschleife verpasst.

Code:
public synchronized void run()
    {
        while(true)
        {
            renderFrame();
            needToWait=true;
            try{
                while(needToWait)
                        wait();
            }
            catch(InterruptedException e)
            { }
        }
}
Wenn nun renderFrame erneut aufgerufen werden soll setze ich mithilfe einer anderen Funktion needToWait auf false und rufe dann renderThread.notify(); auf.
Dabei rendert er aber leider nur genau 1 mal und hängt sich dann auf.. :confused:

Die Kontrolle kommt dabei gar nicht mehr zurück an dir Funktion die den Thread gestartet hat, irgendwie sehr merkwürdig...

Tut mir leid bin noch nicht so der Multithreadingexperte..
 
Zuletzt bearbeitet:
Hm, das sieht mir nach einem klassichen "Deadlock" in einer Endlosschleife aus.

Überleg mal, was passiert, wenn du deinen Thread mit notify() aus dem wait() kickst?

Er springt wieder in das wait(), weil er nicht aus der while-Schleife herauskommt. ;)

Versuch mal:
Code:
public void run() {
  while(true) {
    renderFrame();
    synchronized (this) {
      try{
        wait();
      } catch (InterruptedException e) {
      }
    }
  }
}

Wenn du hier das renderThread.notify() aufrufst, springt er aus dem wait heraus, rendered und wartet im wait() wieder auf das nächste Aufwachen.
 
Ich hatte ja geschrieben, dass ich mein needToWait vorher auf false setze bevor ich notify() aufrufe. So hätte er dann ja eigentlich aus der inneren Schleife entkommen können.

Das Ergebnis mittels deiner Version ist das selbe, da tritt dann auch wieder ein deadlock auf. Ich habe jetzt auch versuchsweise mal nur das renderThread.notify() in einen synchronized block gesetzt, nicht die gesamte aufrufende Funktion..

Mit einigem Konsolen-Logging habe ich nochmal überprüft: der Thread in dem renderThread.start() aufgerufen wird läuft danach gar nicht weiter, ich dachte wait() würde bewirken dass die anderen threads weiterlaufen können?
 
Hast du die start()-Methode in deinem Renderer überschrieben und rufst dort direkt Run auf? wenn ja, hättest du gar keinen neuen Thread erzeugt.

Stammt die Render-Klasse von der Thread Klasse ab (mittels extends) oder implementierst du das Interface Runnable?

Die renderschleife von mir oben darf eigentlich keine Deadlocks erzegen, die verwende ich so bzw. so ähnlich in diversen Programmen. Ich denke, der Hund liegt in der Threaderzeugung oder im notify() begraben. Post mal den relevanten Code bitte.
 
Der Renderer implementiert Runnable und ich habe die start() methode auch nicht überschrieben, mir ist schon klar dass run() und start() verschiedene Sachen machen.

Hier ist der relevante code:

In Renderer (implements Runnable)
Code:
public synchronized void notifyDataAvailable()
    {
        needToWait=false;
        notifyAll();
    }

    /**
     * Run the Thread (calls renderFrame)
     */
    public synchronized void run()
    {
        while(true)
        {
            if(!needToWait)
            {
                System.out.println("Rendering");
                renderFrame();
                needToWait=true;
            }
            else
            {
                try{
                    System.out.println("Waiting");
                    wait();
                }
                catch(InterruptedException e)
                {
                    System.out.println("Interrupted!");
                }
                
            }
        }
    }

Der aufrufende Thread macht folgendes:
Code:
//Initialisierung
Thread renderThread = new Thread(renderer);
boolean renderThreadStarted=false;

//Später jedesmal wenn gerendert werden soll:
if(!renderThreadStarted)
{
renderThreadStarted=true;
renderThread.start();
System.out.println("Render Thread started");
}
synchronized(this){renderer.notifyDataAvailable();}

In der mainloop im aufrufenden Thread wird vorher auch noch getestet ob renderThread.isAlive() wahr ist, da falls das nicht der Fall ist ich eine Exception bekomme dass ich versuche ein OpenGL context doppelt anzusprechen, das kommt mir so vor als würde der renderThread dann doppelt laufen (macht aber keinen Sinn).

Zwischen dieser Überprüfung und dem Aufruf von notifyDataAvailable() werden Daten an den Renderer übergeben, die Methoden sind alle synchronized.
 
Mir ist immer noch nicht klar, wozu du die Variable "needToWait" brauchst, da doch das wait() solange wartet, bis es von einem notify() abgebrochen wird. Das ist also sozusagen "Doppelt gemoppelt". Lass die einfach weg.

Das eigentliche Problem ist die komplett synchronisierte run()-Methode. synchronized lockt das Objekt, auf dem es ausgeführt wird (bei synchronized-Methoden impliziert das this, also das aktuelle Objekt). Dadurch wartet dein Aufruf im oberen Codeteil auf die Freigabe des synchronized-Blocks, die aber nie erfolgt, weil deine run-Methode ja endlos läuft.

Synchronized also nur um die Stellen, die das wirklich brauchen.

Code:
    /**
     * Run the Thread (calls renderFrame)
     */
    public synchronized void run()
    {
        while(true)
        {
            synchronized (this) {
                try{
                    System.out.println("Waiting");
                    wait();
                }
                catch(InterruptedException e)
                {
                    System.out.println("Interrupted!");
                }
                
            }
        }
    }

Code:
//Initialisierung
Thread renderThread = new Thread(renderer);
boolean renderThreadStarted=false;

//Später jedesmal wenn gerendert werden soll: 
if(!renderThreadStarted)
{
    renderThreadStarted=true; 
    renderThread.start();
    System.out.println("Render Thread started"); 
}

synchronized(renderThread){
    renderer.notify();
}

Bitte unbedingt beachten, dass die Hauptschleife nur den unteren Teil (ab dem Kommentar "Später jedesmal wenn gerendert werden soll") eibnschliesst, da du ansonsten immer einen neuen Thread anlegen würdest.
 
Zurück