In meinem Spiel gibt es Grundstücke mit Gebäuden (Häuser, Ressourcenzentren). Gebäude wie Häuser haben Mieter, Räume, Add-Ons usw., und es gibt mehrere Werte, die basierend auf all diesen Variablen simuliert werden müssen.
Jetzt möchte ich AndEngine für das Front-End-Material verwenden und einen weiteren Thread erstellen, um die Simulationsberechnungen durchzuführen (möglicherweise auch später AI in diesen Thread aufnehmen). Dies ist so, dass ein ganzer Thread nicht die ganze Arbeit erledigt und Probleme wie das Blockieren verursacht. Dies führt das Problem der Parallelität und Abhängigkeit ein .
Das Währungsproblem ist mein Haupt-UI-Thread, und der Berechnungsthread müsste beide auf alle Simulationsobjekte zugreifen. Ich muss sie threadsicher machen, weiß aber nicht, wie ich die Simulationsobjekte speichern und strukturieren soll, um dies zu ermöglichen.
Das Abhängigkeitsproblem besteht darin, dass meine Berechnungen zur Berechnung von Werten von den Werten anderer Objekte abhängen.
Was wäre der beste Weg, um mein Mieterobjekt im Gebäude mit meinen Berechnungen zu verknüpfen? Hardcode in die Mieterklasse? Was ist ein guter Weg, um Algorithmen zu "speichern", damit sie leicht optimiert werden können?
Ein einfacher fauler Weg wäre, alles in eine Klasse zu packen, die das gesamte Objekt enthält, wie z. B. Grundstücke (die wiederum die Gebäude usw. halten). Diese Klasse würde auch den Spielstatus wie die dem Benutzer zur Verfügung stehende Technologie und Objektpools für Dinge wie Sprites enthalten. Aber das ist ein fauler und gefährlicher Weg, richtig?
Bearbeiten: Ich habe mir Dependency Injection angesehen, aber wie gut kommt das mit einer Klasse zurecht, die andere Objekte enthält? dh mein Grundstück mit einem Gebäude, das einen Mieter und eine Vielzahl anderer Werte hat. DI sieht auch bei AndEngine wie ein Schmerz im Hintern aus.
quelle
Antworten:
Ihr Problem ist von Natur aus seriell - Sie müssen eine Aktualisierung der Simulation durchführen, bevor Sie sie rendern können. Das Abladen der Simulation in einen anderen Thread bedeutet einfach, dass der Haupt-UI-Thread nichts tut , während der Simulationsthread tickt (was bedeutet, dass er blockiert ist).
Die landläufigen „best practice“ für die Parallelität ist nicht Ihre Darstellung auf einem Thread und Ihre Simulation auf einem anderen zu setzen, wie Sie vorschlagen. Ich empfehle in der Tat dringend gegen diesen Ansatz. Die beiden Operationen sind natürlich seriell miteinander verbunden, und obwohl sie brutal erzwungen werden können, ist sie nicht optimal und skaliert nicht .
Ein besserer Ansatz besteht darin, Teile des Updates oder Renderings gleichzeitig zu erstellen, das Aktualisieren und Rendern jedoch immer seriell zu belassen. Wenn Sie beispielsweise eine natürliche Grenze in Ihrer Simulation haben (z. B. wenn sich Häuser in Ihrer Simulation nie gegenseitig beeinflussen), können Sie alle Häuser in Eimer mit N Häusern schieben und eine Reihe von Threads aufwickeln, die jeweils einen verarbeiten Bucket, und lassen Sie diese Threads verbinden, bevor der Aktualisierungsschritt abgeschlossen ist. Dies lässt sich viel besser skalieren und eignet sich viel besser für das gleichzeitige Design.
Sie überdenken den Rest des Problems:
Die Abhängigkeitsinjektion ist hier ein roter Hering: Alle Abhängigkeitsinjektion bedeutet wirklich, dass Sie die Abhängigkeiten einer Schnittstelle an Instanzen dieser Schnittstelle übergeben ("injizieren"), normalerweise während der Erstellung.
Das heißt, wenn Sie eine Klasse haben, die a modelliert
House
, die Dinge über das wissen muss, in demCity
sie sich befindet, dannHouse
könnte der Konstruktor folgendermaßen aussehen:Nichts Besonderes.
Die Verwendung eines Singletons ist nicht erforderlich (dies geschieht häufig in einigen der wahnsinnig komplexen, überentwickelten "DI-Frameworks" wie Caliburn, die für "Enterprise" -GUI-Anwendungen entwickelt wurden - dies ist keine gute Lösung). Tatsächlich ist die Einführung von Singletons oft das Gegenteil eines guten Abhängigkeitsmanagements. Sie können auch schwerwiegende Probleme mit Multithread-Code verursachen, da sie normalerweise ohne Sperren nicht threadsicher gemacht werden können. Je mehr Sperren Sie erwerben müssen, desto schlimmer war Ihr Problem für die parallele Behandlung geeignet.
quelle
Die übliche Lösung für Parallelitätsprobleme ist die Datenisolation .
Isolation bedeutet, dass jeder Thread seine eigenen Daten hat und keine Daten anderer Threads berührt. Auf diese Weise gibt es keine Probleme mit der Parallelität ... aber dann haben wir ein Kommunikationsproblem. Wie können diese Threads zusammenarbeiten, wenn sie keine Daten gemeinsam nutzen?
Hier gibt es zwei Ansätze.
Erstens ist Unveränderlichkeit . Unveränderliche Strukturen / Variablen sind diejenigen, die ihren Zustand niemals ändern. Das mag zunächst nutzlos klingen - wie kann man eine "Variable" verwenden, die sich nie ändert? Wir können diese Variablen jedoch austauschen ! Betrachten Sie dieses Beispiel: Angenommen, Sie haben eine
Tenant
Klasse mit einer Reihe von Feldern, die sich in einem konsistenten Zustand befinden muss. Wenn Sie einTenant
Objekt in Thread A ändern und es gleichzeitig von Thread B aus beobachten, wird das Objekt in Thread B möglicherweise in einem inkonsistenten Zustand angezeigt. WennTenant
es jedoch unveränderlich ist, kann Thread A es nicht ändern. Stattdessen schafft es neueTenant
Objekt mit nach Bedarf eingerichteten Feldern und tauscht es gegen das alte aus. Das Austauschen ist nur eine Änderung einer Referenz, die wahrscheinlich atomar ist, und daher gibt es keine Möglichkeit, das Objekt in einem inkonsistenten Zustand zu beobachten.Der zweite Ansatz ist Versenden von Nachrichten . Die Idee dahinter ist, dass wir diesem Thread sagen können , was mit Daten zu tun ist, wenn alle Daten einem Thread "gehören" . Jeder Thread in dieser Architektur verfügt über eine Nachrichtenwarteschlange - eine Liste von
Message
Objekten und eine Messaging-Pumpe - eine ständig ausgeführte Methode, die eine Nachricht aus der Warteschlange entfernt, interpretiert und eine Handlermethode aufruft. Angenommen, Sie haben auf ein Grundstück getippt und signalisiert, dass es gekauft werden muss. Der UI-Thread kann das nicht ändernPlot
Objekt nicht direkt , da er zum Logik-Thread gehört (und wahrscheinlich unveränderlich ist). Der UI-Thread erstelltBuyMessage
stattdessen ein Objekt und fügt es der Warteschlange des Logik-Threads hinzu. Der Logik-Thread nimmt beim Ausführen die Nachricht aus der Warteschlange und ruft aufBuyPlot()
Extrahieren der Parameter aus dem Nachrichtenobjekt. Möglicherweise wird eine Nachricht zurückgesendet, in der beispielsweise derBuySuccessfulMessage
UI-Thread angewiesen wird, ein "Jetzt haben Sie mehr Land!" Fenster auf dem Bildschirm. Natürlich muss der Zugriff auf die Nachrichtenwarteschlange mit der Sperre, dem kritischen Abschnitt oder dem in AndEngine genannten Namen synchronisiert werden. Dies ist jedoch ein einzelner Synchronisationspunkt zwischen Threads, und Threads werden für eine sehr kurze Zeit angehalten, sodass dies kein Problem darstellt.Diese beiden Ansätze werden am besten in Kombination verwendet. Ihre Threads sollten mit Nachrichten kommunizieren und einige unveränderliche Daten für andere Threads "offen" haben - beispielsweise eine unveränderliche Liste von Plots, mit denen die Benutzeroberfläche sie zeichnen kann.
Beachten Sie auch, dass "schreibgeschützt" nicht unbedingt unveränderlich bedeutet ! Jede komplexe Datenstruktur wie eine Hashtabelle kann ihren internen Status bei Lesezugriffen ändern. Überprüfen Sie daher zuerst die Dokumentation.
quelle
Wahrscheinlich haben 99% der in der Geschichte geschriebenen Computerprogramme nur einen Thread verwendet und funktionierten einwandfrei. Ich habe keine Erfahrung mit der AndEngine, aber es ist sehr selten, Systeme zu finden, die die Threading erfordern , nur einige, die mit der richtigen Hardware davon hätten profitieren können.
Um Simulation und GUI / Rendering in einem Thread durchzuführen, führen Sie traditionell einfach einen Teil der Simulation durch, rendern dann und wiederholen dies in der Regel viele Male pro Sekunde.
Wenn jemand wenig Erfahrung mit der Verwendung mehrerer Prozesse hat oder nicht genau weiß, was Thread-Sicherheit bedeutet (ein vager Begriff, der viele verschiedene Dinge bedeuten kann), ist es zu einfach, viele Fehler in ein System einzuführen. Daher würde ich persönlich empfehlen, den Single-Threaded-Ansatz zu verwenden, Simulation und Rendering zu verschachteln und alle Threading-Vorgänge für Vorgänge zu speichern, von denen Sie sicher wissen, dass sie lange dauern und unbedingt Threads und kein ereignisbasiertes Modell erfordern.
quelle