2009-11-08 4 views
2

Les gars, je développe une application de jeu multijoueur avec C++ et en train de choisir une architecture multithread appropriée pour elle.C++ multithreading: les verrous explicites dans les classes de modèle de domaine

Le noyau de l'application est la boucle sans fin qui met à jour essentiellement chaque trame toutes les entités du jeu mondial. Actuellement, cette boucle mondiale est singlethreaded. Cela fonctionne très bien mais j'aimerais vraiment le rendre plus évolutif sur les multicœurs.

Comme toutes les entités du monde existent dans les établissements et mis à jour dans chaque trame comme suit:

 
- World::update(dt) //dt is delta time since the last frame 
    - Location::update(dt) 
    - WorldEntity::update(dt) 
    - WorldEntity::update(dt) 
    - ... 
    - Location::update(dt) 
    - WorldEntity::update(dt) 

... Je pensais à l'exécution de chaque emplacement (et sa logique de mise à jour) dans un thread séparé. Cela signifie que j'ai besoin de synchroniser correctement les entités du monde. Et ce que je vraiment ne veux pas faire depuis, je crois, verrouillage explicite dans les classes domaine des méthodes est faux et il fait le développement, le maintien et le débogage très beaucoup plus difficile.

Au début, je pensais à isoler les entités de localisation des entités dans différents endroits en interdisant les appels entre eux. Quels sont les moyens possibles pour y parvenir? Stocker des entités de chaque emplacement dans un stockage local de thread afin qu'ils ne soient pas accessibles de l'extérieur? Ou peut-être à la place d'un thread par emplacement, utilisez plutôt des processus (mais cela compliquera beaucoup). Cependant

même si des entités de localisation sont bien isolées il un autre problème - la persistance. J'ai déjà une sorte de service de persistance générique simple qui s'exécute dans un thread séparé. Il peut être utilisé en mode asynchrone, il accepte un objet à sauvegarder et renvoie un objet futur spécial qui peut être utilisé pour suivre le processus de persistance. J'aimerais utiliser ce service, mais comme il fonctionne dans un thread séparé, je dois de nouveau synchroniser correctement l'accès aux classes de domaine. Dans ce cas, l'option possible pourrait être d'implémenter correctement le clonage des objets de domaine afin que le service de persistance accepte une copie de l'objet à sauvegarder et aucun verrouillage explicite ne serait nécessaire ...

D'où la question, tout est dit ci-dessus en vaut la peine? Ou peut-être devrais-je simplement ajouter une logique de synchronisation explicite dans toutes les classes de domaine et en finir avec elle? Ou peut-être y a-t-il une meilleure option dont je ne suis pas au courant?

Merci à l'avance

Mise à jour ajouté schéma de structure mondiale grâce à Jed Smith

+0

Pouvez-vous visualiser la structure de votre code? Et des interactions entre chaque étapes et fonctions. Le plus important est de trouver des parallélismes dans votre code. Aussi, s'il vous plaît mettez '' pour certains termes comme "World", "Location". Vous voulez paralléliser une grosse boucle. Si la boucle n'a pas de "dépendances bouclées", alors c'est très facile. Cependant, dans votre cas, ce n'est pas le cas. Ensuite, le «parallélisme pipeline» pourrait être mis en œuvre. Oui, je sais qu'il y a des mots à la mode, que vous n'êtes peut-être pas familiers. Mais, il s'agit d'une stratégie générale sur la pararllisation au niveau de la boucle. – minjang

+0

Quel est le meilleur pour le faire ici? En éditant le message original ou en me répondant à moi-même? – pachanga

+0

Oh merci, je l'ai eu. Vous devez également vérifier les "dépendances". Par exemple, WorldEntity :: update a-t-il des dépendances avec l'appel précédent? Qu'en est-il des emplacements? La compréhension des dépendances est la première étape de la parallélisation. – minjang

Répondre

2

Eh bien, quand je faisais des serveurs de jeu MMO, j'ai utilisé mis en scène, un modèle de programmation hautement simultanée. Vous pouvez voir différents modèles de programmation hautement concurrents avant.

Voici une partie d'un modèle Mise en scène:

    ... 
      +-------+-------+   
      | Process Msg & |   
      | Send AckReq |   
      +---------------+   
      |App.MsgStage() |   
      +-------+-------+   
        | Pop()     
^    +-V-+      
| Events  | Q | Msg Stage |  
| Go Up  | 0 | Logic-Half |   
-+------------- | | -------------+-- ... 
| Requests  | | I/O-Half |    
| Move Down +-^-+    |    
V    | Push()        
    +--------------+-------------+     
    | Push OnRecv Event,  |   
    | 1 Event per message  |  
    |       | 
    | Epoll I/O thread for  | 
    |multi-messaging connections | 
    +------^-------^--------^----+ 
      |  |  |       
Incoming msg1 msg2  msg3   

Comme le montre la figure ci-dessus, il est un réseau/messagerie étape comprennent Logic-moitié et d'E/S-moitié. Les 2 font halfs communication à l'aide files d'attente sans verrouillage et/ou tampons circulaires sans verrouillage tels que la file d'attente de l'événement et Request Queue, donc pas de verrouillage/mutex serait nécessaire. Lors de la création d'un MMO complet, d'autres étapes doivent être incluses en dehors de Messaging du côté serveur, par exemple Database Stage pour charger/stocker des joueurs, AI/Timer Stage pour régénérer des monstres ou des ressources et Logger Stage pour la journalisation. sur.En général, la moitié d'E/S ou la moitié inférieure d'une étape est responsable des E/S ou d'autres tâches fastidieuses telles que la soumission d'une requête "select" à la base de données, l'écriture de données dans un fichier disque ou l'envoi d'un un message provenant d'une interface réseau, etc., qui peut être bloqué; tandis que Logic-half est un calcul logique pur, aucune opération d'E/S et ne sera jamais bloqué.

Depuis Logic-moitié pourrait être exécuté par CPU très rapidement, toutes les étapes() (logique moitié) tels que MsgStage() ou TimerStage() peut être exécuté en un seul StagedModel.Stage() appeler séquentielle, dites le fil principal. Et chaque moitié inférieure pourrait avoir un ou deux fils, disons demi-fils. Par exemple, comme nous l'avons testé, sur une machine Linux 2.6, un seul thread EPOLL devrait suffire pour les multi-liseners et des milliers de clients de messagerie. Si Win/MSVC vous utiliserez le port d'achèvement au lieu de EPOLL. De cette façon, pour un serveur de jeu MMO lourd, vous n'avez que quelques threads au total, et ils sont optimisés pour l'architecture d'un ordinateur multicœur, car chaque cœur exécute deux ou trois threads par processeur à deux cœurs, ou un ou deux threads par processeur 4-core. Encore une fois, vous pouvez utiliser des files d'attente sans verrou et/ou des tampons annulaires sans verrou, et vous saurez dans le modèle par étapes que la plupart des files d'attente ou des tampons annulaires ont un seul producteur et un seul consommateur. Donc, en fonction de vos préoccupations, le monde pourrait être associé à une scène (par exemple Scène, AI ou Timer ou autre), et le faire en un seul fil inférieur, noter seulement un fil pour tous vos emplacements et Cela devrait suffire. Ainsi, vous ne devez pas déclencher la mise à jour de tous les emplacements en même temps, bien que vous puissiez toujours le faire si vous le souhaitez. Dans la moitié inférieure, par ex. SceneStageThread, événement de mise à jour (avec LocationID + WorldEntityID) sera généré lors de la mise à jour, et votre moitié logique, par ex. SceneStage(), le OnUpdate (& UpdateEvent) le gèrera pour mettre à jour l'emplacement et WorldEntity lorsque MainThread appelle SceneStage(). Si vous le souhaitez, le SceneStageThread pourrait générer d'autres événements tels que MonsterRelive événement, etc.

Voir le document EffoNetMsg.pdf à http://code.google.com/p/effonetmsg/downloads/list ou EffoAddons.pdf à http://code.google.com/p/effoaddon/downloads/list pour en savoir plus sur les modèles de programmation hautement simultanées (y compris un modèle complet Mise en scène) et la messagerie réseau; Pour en savoir plus sur les fonctionnalités sans verrou, telles que la file d'attente sans verrou et le tampon annulaire sans verrou, consultez EffoDesign_LockFree.pdf au http://code.google.com/p/effocore/downloads/list.

Effo EDIT @ 2009nov09, en ajoutant des références étagées C++ URL de l'interface et le code de mise en œuvre:

Interface: 
http://code.google.com/p/effoaddon/source/browse/trunk/devel/effo/codebase/addons/staged/include/staged_i.h 
Implementation: 
http://code.google.com/p/effoaddon/source/browse/trunk/devel/effo/codebase/addons/staged/src/staged.cpp 
+0

Merci pour les liens, j'ai besoin de temps pour les digérer avant de répondre quelque chose de significatif;) – pachanga

+0

Hm ... après avoir lu tous les liens ci-dessus, il semble un peu déroutant. Corrigez-moi si je me trompe, mais il semble qu'EffoNet mélange des réseaux avec un modèle de concurrence que je n'aime pas vraiment. J'ai déjà mis en réseau avec boost :: asio que je trouve très flexible et complètement satisfaisant mes besoins. – pachanga

+0

Eh bien, une étape est composée de quelques étapes qui peuvent contenir des E/S réseau, des E/S de disque, des E/S de base de données et des minuteries, et ainsi de suite. Donc, dans ma réponse, Network I/O Stage est juste un exemple d'étape; c'est-à-dire qu'une application basée sur un modèle de programmation hautement concurrent peut ne pas avoir d'étape d'E/S réseau/messagerie. – Test

Questions connexes