Warum Thread für Spieleprogrammierung?

  • Themenstarter Themenstarter yyannekk
  • Beginndatum Beginndatum
Y

yyannekk

Gast
Es wird ja immer empfohlen einen Thread für die mainloop zu nehmen (hab aber keine Erklärung gefunden). Habe gerade getestet dass es auch ohne geht, deswegen meine Frage...
Ich vermute dass die parallele Ausführung von Programm Code und Annehmen und Ausführen von Benutzereingaben dann besser funktioniert. Vielleicht hat da ja jemand Detailwissen.
 
parallele Ausführung von Programm Code und Annehmen und Ausführen von Benutzereingaben dann besser funktioniert
ja genau um das gehts. sonst kannst du nicht auf benutzer eingaben "horchen" wenn das spiel gerade zeichnet oder etwas berechnet...
 
Getestet habe ich ein Spiel welches einen Thread erzeugt in dem die mainloop läuft.
Habe die Klasse so umgebaut dass sie nicht von Thread erbt und new MainLoopClass().start() durch new MainLoopClass().run() (so vom Prinziep her...) ersetzt...
 
Wenn du das als letztes in der main aufrufst, dann verwendest du den Thread eben dieser Methode weiter.
Nur in dem Thread von Swing, der die Benutzereingaben annimmt, solltest du nichts weiter tun als eben das.
 
Geht man von der Annahme aus, dass man kein triviales Spiel programmiert, so dürfte dies doch einiges an Rechnerkapazität in Anspruch nehmen. Sei es die Berechnung einer komplexen KI, oder die Berechnung komplexer Spielszenen und das damit verbundene Rendern.

Nur weil dein triviales Spiel, auch ohne Parallelisierung gut funktioniert, ist das kein Anzeichen für irgendetwas.

Jetzt gibt es mind. 3 Gründe, warum man parallelisieren sollte (Annahme kein triviales Spiel):
- Effizienz der damit verbundenen Algorithmen
- Datenparallelisierung, so arbeitet eine Grafikkarte extrems parallel, d.h. die Berechnung der Pixel findet stark parallelisiert statt, weil es ansonsten gar nicht mit dem Aufwand klar kommen würde.
- Unabhängigkeit der Programmausführung (z.B. keine Abhängigkeiten bei einer GUI)
- daneben gibt es noch zig Gründe, einfach mal Googeln oder Wikipedia ist auch eine gute Anlaufstelle, aber das dürfte ja wohl klar sein....
 
Mal ein einfaches Beispiel:
Dein Spiel soll unabhängig vom Pc auf dem es läuft mit sagen wir mal 60 Updates pro Sekunde laufen - damit du bewegungen etc einfacher darstellen kannst.
Von der Rechenleistung ist der PC aber in der Lage 600 Updates pro Sekunde zu berechnen.
Das bedeutet du musst den Thread deines Programms zwischen den Updates ausbremsen (i.d.R. mit sleep() ).
Da dein Thread aber nur 10% seiner Kapazität arbeitet wird er 90% einer Sekunde auf sleep() stehen.
In dieser Zeit werden auch Benutzereingaben nur mit Glück im Programm landen - und 10% Chance darauf, dass eine Eingabe angenommen wird sind schon sehr gering, gerade bei Spielen wo man viel und schnell herumklicken will.
(Das Beispiel ist etwas sehr konstruiert 😉 aber es hilft hoffentlich die Problematik zu veranschaulichen)
Gruß
 
Jetzt gibt es mind. 3 Gründe, warum man parallelisieren sollte (Annahme kein triviales Spiel):
...und es gibt min. 4 Gründe, warum man es nicht tun sollte:

  • Der Spielablauf ist nicht mehr deterministisch.
  • Die VM garantiert nicht, wann welche Threads drankommen. Wenn es schlecht läuft, "verhungern" Threads.
  • Der Aufwand für Synchronisation steigt.
  • Der Code wird komplexer und schwieriger wartbar.

Threads sind fraglos dort sinnvoll, wo man auf IO wartet...also z.B. bei Netzwerkzugriffen, verzögertem Laden usw. Sie sind u.U. dort sinnvoll, wo Dinge einfach zu parallelisieren sind und im Ergebnis mehr Performance versprechen...also z.B. bei einem Softwarerenderer. Sie sind ebenfalls sinnvoll, wo eine nebenläufige Berechung sehr lange dauert...also z.B. die KI in einem Schachspiel, wenn parallel zum "Denken" das Spielfeld gezeichnet werden soll.
Es ist nicht sinnvoll, Dinge parallel auszuführen, nur weil es geht, man aber an sich kein Performanceproblem hat. Man schafft damit nur Raum für Fehler und unnötige Komplexität. Viele Ansätze die ich bisher gesehen habe, ignorieren nämlich die Tatsache, dass z.B. ein Renderthread und ein Logikthread teilweise parallel auf denselben Datem arbeiten und es damit alle Arten von lustigen Seiteneffekten geben kann, wenn man nicht gescheit synchronisiert.

Meine Empfehlung ist daher: Threads für IO (wo sinnvoll) und lange nebenläufige Berechnungen. Wenn man mit den Standard-Java-Methoden für Eingaben arbeitet (also irgendwelche Eventlistener benutzt), laufen die sowieso in einem eigenen Thread, also muss man da gar nichts extra tun. Keine Threads für die Spiellogik, sofern man nicht in massive Performanceprobleme läuft. Es macht mehr Ärger als er nutzt.
 
Es ist nicht sinnvoll, Dinge parallel auszuführen, nur weil es geht, man aber an sich kein Performanceproblem hat.
Performance-Gewinnung ist nur ein Einsatzgebiet von Multi-Threading. Ein anderes liegt im Verlagern des Scheduling auf das OS. Es ist sogar so dass der Großteil von Multi-Threaded Anwendungen genau aus diesem Grund, und eben nicht wg. der Performance, auf mehrere Threads zurückgreift. Beispiel: Deine Notizbuch-Software checkt im Hintergrund auf anstehende Termine, um dich zu alamieren. Mit Performance hat das nix zu tun, auch nicht mit I/O. Und auch nicht damit, dass der main-Thread oder der EDT gerade viel beschäftigt sind und keine Zeit dafür hätten. Es geht einfach darum dass du nicht manuell im Code festlegen musst, wann genau was und wo passiert. Denn oft ist das einfach egal. Versuch mal, so einen regelmässigen Check in deinen main-Thread zu integrieren. Ist sogut wie nicht möglich. Also startet man einen neuen Thread, und das OS kümmert sich drum, dass er regelmässig ausgeführt wird. Die Sache mit dem "Verhungern" ist da in 99% der Fälle nicht von Belang, darum muss man sich nur in Extremfällen kümmern.
 
Zuletzt bearbeitet:
Performance-Gewinnung ist nur ein Einsatzgebiet von Multi-Threading. Ein anderes liegt im Verlagern des Scheduling auf das OS. Es ist sogar so dass der Großteil von Multi-Threaded Anwendungen genau aus diesem Grund, und eben nicht wg. der Performance, auf mehrere Threads zurückgreift...
Ja, schon klar. Das hat aber eher nix mit Spielen zu tun und darum geht es hier ja. Es gibt gerade im Java-Umfeld die Tendenz, alles mit Threads zu erschlagen. Und für performante und ruckelfreie Spiele ist das einfach eine schlechte Idee. Und das wollte ich nur rüberbringen.
 
Es gibt gerade im Java-Umfeld die Tendenz, alles mit Threads zu erschlagen. Und für performante und ruckelfreie Spiele ist das einfach eine schlechte Idee.
Ach, papperlapapp!

Java:
for(final Bullet bullet: player.getDoubleActionMinigun().getFiredBullets()){
     new Thread(){
         @Override
         public void run(){
             bullet.flyOnePixel();
         }
     }.start();
}

Läuft 1A. 😀
 
Zuletzt bearbeitet:
Haha ja damn das ist mir auch aufgefallen. Ich hatte gehofft du hast es nicht gemerkt 😀 Naja, danke für den Link, vllt kann ich daran ja echt noch was optimieren 😉
 
Ach, papperlapapp!

Java:
for(final Bullet bullet: player.getDoubleActionMinigun().getFiredBullets()){
     new Thread(){
         @Override
         public void run(){
             bullet.flyOnePixel();
         }
     }.start();
}

Läuft 1A. 😀

Lach nicht, ich habe mal einem Studenten bei einer Aufgabe geholfen, in der es um ein Schneegestöber ging, d.h. es sollten eben einige hunderte Schneeflocken gezeichnet werden, die zu Boden fallen und ne Schneedecke bilden.
Jede Flocke sollte laut Aufgabe ein Thread sein. Hat selbst auf meinem doch recht schnellen PC schon mit ein paar hundert Flocken angefangen die CPU ordentlich zu quälen.
Ein toller Prof, der sich so etwas ausdenkt :autsch:
 
Threads lohnen sich wie schon oben gesagt bei verteilten Aufgaben. ID hatte zur aktuellen ID Tech 5 Engine und der Virtual Texture ein hübsches PDF zur Siggraph mitgebracht gehabt. Da ist unter anderem auch enthalten welche Zeit für den jeweiligen AUfwand vorgesehen ist.
http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf

Aus dem PDF hat gesagt.:
id Tech 5 does a lot of processing
–Animation blending ~2 msec
–Collision detection ~4 msec
–Obstacle avoidance ~4 msec
–Transparency sorting ~2 msec
–Virtual texturing ~8 msec
–Misc processing ~4 msec
–Rendering ~10 msec
–Audio ~4 msec
Und das bei stabilen 60Hz 😉
 
Wo ist das problem mit dem deterministisch? Warum nehmen immer alle an das man Threads nicht intelligent machen kann?

Man hat nen normalen update tick, aber statt
physic();
ki();
rendern();

macht man alle 3 gleichzeitig und am ende wartet man bis jeder fertig ist, synchronisiert die daten , und geht erst dann in den nächsten tick. Schon ist wieder alles schön deterministisch.
 
Ne, in diesem Beispiel ist es in der Tat extrem schwer das sinnvoll und determnisitsch zu implementieren. Ein sychronized bringt dir keinen Determinismus. Die Methoden hängen doch irgendwie voneinander ab. Also wenn grad was in der Phsysik berechnet wird, zB wie sich ein Objekt x dreht weil es grad fällt, und die KI auch etwas mit x berechnet, dann was? Dann kommt's drauf an wer zuerst das Lock auf dieses Objekt bekommen hat, phsycis() oder ki(). Und das ist vom Scheduling abhängig und nicht-deterministisch.. Also, da ist schon was dran.
 
...macht man alle 3 gleichzeitig und am ende wartet man bis jeder fertig ist, synchronisiert die daten , und geht erst dann in den nächsten tick. Schon ist wieder alles schön deterministisch.

Das ist aber deutlich komplexer, als einfach die drei Methoden in Threads auszufuehren, da die ja von einander abhaengen. Z.B. veraendert die Physik sicherlich die Positionen der zu rendernden Objekte. Also muss man pro Objekt zwei Positionen (gilt fuer alles von der Physik "beschreibbare", wie Geschwindigkeiten, Beschleunigungen, etc) speichern (die jeweils letzte und die von der Physik gerade neu berechnete). Gerendert werden dann die letzten Positionen, die KI rechnet auch mit den alten Daten. Anschliessend muessen die neuen Daten zu den alten Daten werden (per Kopieren oder irgendwie geschickt per Referenzenaustausch).

Das ist aber alles andere als trivial - und kostet zusaetzlich Performance, die erstmal durch die Threads wieder reingeholt werden muss, bevor sich das alles ueberhaupt lohnt.
 
Multithreading ist unausweichbar, sonst kannst du jeden Quad oder Dual Core in die Tonne kloppen und stattdessen wieder eure AMD XP 2600 rauskramen 😀 Und ja, es lohnt sich Threads zu benutzen und ein gutes OS wird Threads auch nicht verhungern lassen. Klar ist Synchronisierung ein komplexes Gebiet, aber Programmieren soll ja nich zu einfahc sein und ein wenig Kopfarbeit doch noch benötigen 😉
 
Haaach, solche pauschalen Aussagen sind doch Unfug.

Ein Mehrkernsystem bringt doch schon alleine deswegen sehr viel, weil ein modernes Betriebssystem von Haus aus Dutzende Tasks am Laufen hat. Deshalb jetzt auf Krampf jeden Pups multitaskfaehig zu machen, ist einfach mit Kanonen auf Spatzen geschossen.
Ich wuerde mich zur Aussagen hinreissen lassen, dass die allermeisten Spiele, die von Leuten hier im Forum entwickelt werden, besser nur genau einen Thread benutzen (evtl. noch ein oder zwei weitere fuer Netzwerk).

Ein Tetris oder Pacman braucht keine zwei Tasks. Natuerlich, zum Ausprobieren und Herumexperimentieren kann man auch mit mehreren Threads arbeiten - aber als Tipp fuer einen Anfaenger/Einsteiger ist das imho voellig unpassend.

Mal nur so als Info: Minecraft ist singlethreaded und funktioniert hervorragend. Besser man baut ein stabiles, funktionierendes und langsames singlethreaded Spiel, als ein verbuggtes und schnelles multithreaded Spiel.
 
Ich verstehe nicht ,warum multithreading+fehleranfällig in einem Satz so häufig hier genannt werden. Mehrere Threads bringen mehr Komplexität, sprich jemand der denkt es gäbe nur synchronized und wait und notify, der sollte in der tat die Finger von Multithreading lassen und lieber eine sicherere Variante mit einem Thread schreiben. Java ist eine der wenigen Sprachen mit solch umfangreichen Multithreading Optionen und diese zu ignorieren oder nur für seltene Fälle zu gebrauchen, halte ich für falsch. Wenn man dann noch viel Ahnung von so exotischen Saachen wie "Lock-Free-Programming" hat, kann man wirklich parallel verarbeiten lassen.


Außerdem stimme ich dir zu, dass es bei simplen Spielen nicht nötig ist, aber dennoch machen mehrere Threads durchaus Sinn. Die JME z.B. trennt Physik und Grafik, zwar gibt es auch Singlethread-Mode, dieser wird auch laut Seite glaub ich empfohlen, aber das heißt ja nichts.

Nun zu deinem Minecraft-Beispiel: Ich nehme an, du hast dir das Spiel genaustens unter die Lupe genommen, um das zu behaupten (Vermutlcih, weil du grad auch sowas ähnlcihes versuchst, oder ? Ich meine mich da an einen Thread zu errinnern). Jedoch weiß ich nicht inwiefern das als Beweiß gesehen werden kann, dass ein Thread ausreicht. Ich persönlich habe nur Spielererfahrung und empfinde das Spiel an vielen Stellen rucklig und unprofessionell. Woran das liegt, traue ich mich jetzt hier nicht zu mutmaßen. Es gibt aber ebenfalls eine JME-Version (Ganz frühe Alpha^^), genannt Mythruna, welche dieses Spielprinzip sehr viel schöner und ruckelfreier umsetzt. Ich vermute mal, das lieght auch daran, dass Minecraft eine komplette Do-It-Yourself-Produktion ist und dadruch natürlich nichtmal ansatzweise derart viel Erfahrung drinsteckt, wie bei JME.

Zum Schluss noch eine SAche: Threads allein würde ich auch garnicht mehr verwenden. Die Handhabung erfordert viel Boilerplate-Code. Mit einem ExecutorService werden die Thread sauber gemanaged und bei kleinen kurzlebigen Tasks bringt das viel Gewinn.

Gruß,
Chris
 
Ne, in diesem Beispiel ist es in der Tat extrem schwer das sinnvoll und determnisitsch zu implementieren. Ein sychronized bringt dir keinen Determinismus. Die Methoden hängen doch irgendwie voneinander ab. Also wenn grad was in der Phsysik berechnet wird, zB wie sich ein Objekt x dreht weil es grad fällt, und die KI auch etwas mit x berechnet, dann was? Dann kommt's drauf an wer zuerst das Lock auf dieses Objekt bekommen hat, phsycis() oder ki(). Und das ist vom Scheduling abhängig und nicht-deterministisch.. Also, da ist schon was dran.

Wieso, die ki gibt für den nächsten tick an die physic befehle, aka vorwärts laufen.
Bei dem System dürfen die teile nie was woanders manipulieren, sonden schicken messages, an die anderen subsysteme. (Die dann theoretisch auch auf verschiedenen Computern sein können).

Wenn dann sichergestellt werden kann dass Messages garantiert im nächste tick von den jeweiligen zuständigen subsystem verarbeitet werden, wo ist dass dann nicht deterministisch?

ps. ..Locks, würdest du das ernsthaft so regeln wollen 😵
 
Zuletzt bearbeitet:
Ein Mehrkernsystem bringt doch schon alleine deswegen sehr viel, weil ein modernes Betriebssystem von Haus aus Dutzende Tasks am Laufen hat. Deshalb jetzt auf Krampf jeden Pups multitaskfaehig zu machen, ist einfach mit Kanonen auf Spatzen geschossen.
Ich wuerde mich zur Aussagen hinreissen lassen, dass die allermeisten Spiele, die von Leuten hier im Forum entwickelt werden, besser nur genau einen Thread benutzen (evtl. noch ein oder zwei weitere fuer Netzwerk).
Das ist genau der Punkt. Natürlich kann man alles beliebig kompliziert machen...aber dann wird das Spiel nie fertig, hat potentiell mehr Fehler und ob es dann wirklich schneller läuft, darf man auch bezweifeln. Und letztendlich ist es doch egal, ob was nun mit 60 oder 80 FPS läuft. Wenn das der Punkt wäre, wären wir doch alle nicht in diesem Forum, sondern in einem für C++. Ich bin z.B. dazu übergegangen, meinen Kram zu deckeln, auch wenn kein Vsync an ist. Robombs z.B. macht bei 75fps zu. Das erzeugt weniger Abwärme, Lärm und schont auf Laptops den Akku. Natürlich kann ich diverse Multithreading-Optionen aktivieren, einfach weil jPCT sie schon mitbringt. Ohne Limit wird das dann auch schneller...aber wozu? Mit Limit wird dann einfach nur "schneller gewartet".
 
Btw @ EgonOlsen

Es gibt hier auch leute die Programme schreiben die zu komplex sind für ohne multithreading.
Derzeit brachen bei mir physicberechnungen bis zu 40ms, bei einer gewünschten ticktime von 50ms ist es wenn man das ganze noch rendern und ki haben will schon sinnvoll zu multithreaden.
 

Zurück
Oben