Dans mon jeu, il y a des parcelles de terrain avec des bâtiments (maisons, centres de ressources). Les bâtiments comme les maisons ont des locataires, des chambres, des modules complémentaires, etc., et plusieurs valeurs doivent être simulées en fonction de toutes ces variables.
Maintenant, j'aimerais utiliser AndEngine pour les choses frontales et créer un autre thread pour faire les calculs de simulation (peut-être aussi plus tard inclure l'IA dans ce thread). C'est ainsi qu'un thread entier ne fait pas tout le travail et cause des problèmes tels que le blocage. Cela introduit le problème de la concurrence et de la dépendance .
Le problème de devise est mon thread d'interface utilisateur principal et le thread de calcul devrait tous les deux accéder à tous les objets de simulation. Je dois donc les rendre thread-safe, mais je ne sais pas comment stocker et structurer les objets de simulation pour permettre cela.
Le problème de dépendance est que pour calculer des valeurs, mes calculs dépendent des valeurs d'autres objets.
Quelle serait la meilleure façon de relier mon objet locataire dans le bâtiment à mes calculs? Le coder en dur dans la classe des locataires? Quelle est la bonne façon de faire des algorithmes de "stockage" pour les ajuster facilement?
Une manière paresseuse simple serait de tout regrouper dans une classe qui contient tout l'objet, comme les parcelles de terrain (qui à leur tour détiennent les bâtiments, et cetera). Cette classe contiendrait également l'état du jeu tel que la technologie disponible pour l'utilisateur, les pools d'objets pour des choses telles que les sprites. Mais c'est une façon paresseuse et dangereuse, n'est-ce pas?
Edit: je regardais l'injection de dépendance, mais dans quelle mesure cela fait-il face à une classe qui contient d'autres objets? c'est-à-dire mon terrain, avec un immeuble, qui a un locataire et une foule d'autres valeurs. DI ressemble également à une douleur dans le cul avec AndEngine.
la source
Réponses:
Votre problème est intrinsèquement série - vous devez effectuer une mise à jour de la simulation avant de pouvoir la rendre. Le déchargement de la simulation vers un thread différent signifie simplement que le thread d'interface utilisateur principal ne fait rien pendant que le thread de simulation coche (ce qui signifie qu'il est bloqué).
La «meilleure pratique» couramment utilisée pour la concurrence n'est pas de mettre votre rendu sur un thread et votre simulation sur un autre, comme vous le proposez. En fait, je déconseille fortement cette approche. Les deux opérations sont naturellement liées en série, et bien qu'elles puissent être forcées brutalement, elles ne sont pas optimales et ne sont pas évolutives .
Une meilleure approche consiste à rendre des parties de la mise à jour ou du rendu simultanées, mais de laisser la mise à jour et le rendu eux-mêmes toujours en série. Ainsi, par exemple, si vous avez une limite naturelle dans votre simulation (par exemple, si les maisons ne s'influencent jamais dans votre simulation), vous pouvez pousser toutes les maisons dans des seaux de N maisons et faire tourner un tas de fils que chacun traite un et laissez ces threads se joindre avant la fin de l'étape de mise à jour. Cela évolue beaucoup mieux et convient beaucoup mieux à la conception simultanée.
Vous pensez trop au reste du problème:
L'injection de dépendances est un redingue ici: tout ce que signifie l'injection de dépendances est que vous passez ("injectez") les dépendances d'une interface aux instances de cette interface, généralement pendant la construction.
Cela signifie que si vous avez une classe qui modélise un
House
, qui doit savoir des choses sur leCity
contenu, leHouse
constructeur peut ressembler à ceci :Rien de spécial.
L'utilisation d'un singleton n'est pas nécessaire (vous le voyez souvent dans certains des "cadres DI" incroyablement complexes et sur-conçus comme Caliburn qui sont conçus pour les applications GUI "d'entreprise" - cela n'en fait pas une bonne solution). En fait, l'introduction de singletons est souvent l'antithèse d'une bonne gestion des dépendances. Ils peuvent également causer de graves problèmes avec le code multithread car ils ne peuvent généralement pas être rendus thread-safe sans verrous - plus vous devez acquérir de verrous, pire votre problème était adapté à une gestion parallèle.
la source
La solution habituelle pour les problèmes de concurrence est l' isolement des données .
L'isolement signifie que chaque thread a ses propres données et ne touche pas les données des autres threads. De cette façon, il n'y a pas de problèmes avec la concurrence ... mais alors nous avons un problème de communication. Comment ces threads peuvent-ils fonctionner ensemble s'ils ne partagent aucune donnée?
Il y a deux approches ici.
Le premier est l' immuabilité . Les structures / variables immuables sont celles qui ne changent jamais leur état. Au début, cela peut sembler inutile - comment peut-on utiliser une "variable" qui ne change jamais? Cependant, nous pouvons échanger ces variables! Prenons cet exemple: supposons que vous ayez une
Tenant
classe avec un tas de champs, qui doit être dans un état cohérent. Si vous modifiez unTenant
objet dans le thread A et observez-le en même temps à partir du thread B, le thread B peut voir l'objet dans un état incohérent. Cependant, siTenant
est immuable, le thread A ne peut pas le changer. Au lieu de cela, il crée de nouveauxTenant
avec des champs configurés selon les besoins et les échange avec l'ancien. L'échange n'est qu'un changement vers une référence, qui est probablement atomique, et donc il n'y a aucun moyen d'observer l'objet dans un état incohérent.La deuxième approche est la messagerie . L'idée derrière cela est que lorsque toutes les données sont "détenues" par un thread, nous pouvons dire à ce thread quoi faire avec les données. Chaque thread de cette architecture a une file d'attente de messages - une liste d'
Message
objets et une pompe de messagerie - qui exécute constamment une méthode qui supprime un message de la file d'attente, l'interprète et appelle une méthode de gestionnaire. Par exemple, supposons que vous ayez exploité une parcelle de terrain, signalant qu'elle doit être achetée. Le thread d'interface utilisateur ne peut pas modifierPlot
directement l' objet, car il appartient au thread logique (et est probablement immuable). Le thread d'interface utilisateur construit donc unBuyMessage
objet à la place et l'ajoute à la file d'attente du thread logique. Le thread logique, lorsqu'il est en cours d'exécution, prend le message de la file d'attente et appelleBuyPlot()
, extraire les paramètres de l'objet message. Cela pourrait renvoyer un message, par exempleBuySuccessfulMessage
, demandant au thread d'interface utilisateur de mettre en place un "Maintenant, vous avez plus de terrain!" fenêtre à l'écran. Bien sûr, l'accès à la file d'attente de messages doit être synchronisé avec le verrou, la section critique ou tout autre nom dans AndEngine. Mais il s'agit d'un seul point de synchronisation entre les threads, et les threads sont suspendus pendant une très courte période, donc ce n'est pas un problème.Ces deux approches sont mieux utilisées en combinaison. Vos threads doivent communiquer avec les messages et avoir des données immuables "ouvertes" pour d'autres threads - par exemple, une liste immuable de tracés pour l'interface utilisateur pour les dessiner.
Notez également que "lecture seule" ne signifie pas nécessairement immuable ! Toute structure de données complexe comme une table de hachage peut changer son état interne sur les accès en lecture, donc vérifiez d'abord avec la documentation.
la source
Probablement 99% des programmes informatiques écrits dans l'histoire n'utilisaient qu'un seul thread et fonctionnaient bien. Je n'ai aucune expérience de l'AndEngine mais il est très rare de trouver des systèmes qui nécessitent du threading, juste plusieurs qui auraient pu en bénéficier, avec le bon matériel.
Traditionnellement, pour faire de la simulation et de l'interface graphique / rendu dans un seul thread, vous faites simplement un peu de simulation, puis vous effectuez un rendu et vous répétez, généralement plusieurs fois par seconde.
Quand quelqu'un a peu d'expérience de l'utilisation de plusieurs processus ou ne comprend pas complètement ce que signifie la `` sécurité '' des threads (qui est un terme vague qui peut signifier beaucoup de choses différentes), il est trop facile d'introduire de nombreux bogues dans un système. Donc, personnellement, je recommanderais d'adopter l'approche à un seul thread, d'entrelacer la simulation et le rendu, et d'enregistrer tout thread pour des opérations dont vous savez avec certitude qu'elles vont prendre beaucoup de temps et nécessiteront absolument des threads et non un modèle basé sur des événements.
la source