Bienvenue, Invité. Merci de vous connecter ou de vous inscrire.
Avez-vous perdu votre e-mail d'activation ?

Auteur Sujet: Comment bien utiliser les mutex ?  (Lu 34879 fois)

0 Membres et 1 Invité sur ce sujet

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Le design pourtant que j'utilise est exactement le même que celui de la plupart des frameworks  qui permettent de créer des jeux c'est à dire :

-Le "network" : contient tous les objets gérant le réseau.
-Le "world" : contient tous les objets du monde et la physique.
-Et le dernier gros module qui comprends tout ce qui gère l'affichage et les évènement.

Les objets des différents modules doivent toujours faire appels aux modules pour pouvoir communiquer avec des objets d'autres modules, car, les modules gèrent des objets en interne et ceux-ci ne doivent pas être accessible directement sans devoir passer par ces modules.

Bref ces gros modules contiennent tous des systèmes qui ne font que de stocker des informations et remettre à jour les objets. (Certains système utilisent des threads.)
Le but est de limiter un maximum les accès concurrents. (Le thread ajoute l'information dans une structure tandit que l'autre thread vérifie si il a reçus une information et ensuite met à jour si il y a lieu.)
Mais bon je ne peux pas toujours faire comme cela, car, parfois par exemple, les informations sont destinées au serveur ou à un canal, et ses canaux doivent tourner en parallèle avec le serveur, donc là, il peut y avoir des accès concurrents, il y a aussi des accès concurrent sur les sockets de la SFML, là je n'ai pas le choix vu que les sockets de l'API réseau apparemment ne sont pas thread safe. (Je pourrai modifier le code de la SFML pour que ce soit le cas mais j'ai préféré modifier mon code pour limiter les appels à lock et unlock trop nombreux quand j'utilise plusieurs sockets différent qui reçoivent des données à chaque tour de boucle ce qui est mon cas.)
Donc bon je pense pas que le design pose vraiment problème, d'ailleurs, au niveau exécution ça a toujours bien tourné à part se petit pépin que j'ai cité plus haut.

J'ai réglé le problème et ça retourne correctement, je vais pouvori entamer les quêtes, les sors avec système de shader, particule et tout le gameplay pour enfin finir ce petit projet SFML. (Avec à la fin, les sons, que je n'ai pas encore pu tester, là aussi il y a pas mal de thread à ce qu'il parait ça va encore être la joie. ^^)

Bref je vais m'arrêter là, je ne voudrai pas rentrer dans des débats, qui n'ont aucun sens.
« Modifié: Juillet 25, 2013, 02:00:10 pm par Lolilolight »

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bon j'ai le même problème avec les thread du c++11, je suppose donc que pour que les mutex lock bien les variable, il faut initialiser les variables partagé entre plusieurs thread après avoir initialisé le thread. (Ou alors, pas avant l'appel d'un constructeur, c'est à dire, pas à l'initialisation d'une variable statique dans le .cpp.)
Je m'y connais pas trop en accès concurrent donc..., je sais pas trop. :/
« Modifié: Juillet 30, 2013, 09:36:30 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Si tu nous montrais un code minimal de ce que tu fais (qui reproduit bien le problème), on pourrait t'expliquer ce qui se passe.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ok je vais essayer de reprendre le code minimal que j'ai posté plus haut, mais cette fois là avec plusieurs threads, on va voir si ça va crasher.

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bon, déjà là j'ai un code qui crash, et pourtant, ma configuration est la même que pour dans mon plus gros projet, j'utilise exactement la même version de SFML.

Le fichier globals.h qui contient le mutex :

#ifndef GLOBALS_H
#define GLOBALS_H
static sf::Mutex globalMutex;
#endif // GLOBAL_H
 

Le fichier mythread.h (c'est ce qui remet à jour la position des entités dans le monde, dans mon plus gros projet.)
#ifndef MY_THREAD
#define MY_THREAD
#include <SFML/System.hpp>
#include "truc.h"
#include "bidule.h"
#include "globals.h"
#include <random>

class MyThread {
public :
    MyThread ();
    void start ();
private :
    void run ();
    Truc* truc;
    sf::Thread m_thread;
};
#endif // MY_THREAD
 

Le fichier mythread.cpp
#include "mythread.h"
using namespace std;
MyThread::MyThread () : m_thread (&MyThread::run, this) {
    truc = Bidule::getTruc();
}
void MyThread::start () {
    m_thread.launch();
}
void MyThread::run () {
    while (true) {
        globalMutex.lock();
        int iVar = rand() % 100;
        truc->setIVar(iVar);
        globalMutex.unlock();
    }
}
 
bidule.h (ce qui correspond au World dans mon plus grand projet.)
#ifndef BIDULE
#define BIDULE
#include "truc.h"
class Bidule {
public :
       static Truc* getTruc() {
           if (truc == nullptr) {
               static Truc *t = new Truc(5);
               return t;
           }
           return truc;
       }
       static int& getIVar ();
       static void setIVar(int ivar);
private :
       static Truc *truc;
};
#endif // BIDULE
 
bidule.cpp.
#include "bidule.h"
Truc* Bidule::truc = nullptr;
int& Bidule::getIVar () {
    return truc->getIVar();
}
void Bidule::setIVar (int iVar) {
    truc->setIVar(iVar);
}
 

le fichier truc.h (ce qui correspond à la map dans mons plus gros projet.)
#ifndef TRUC_H
#define TRUC_H
class Truc {
private:
        int m_iVar;
public:
        Truc (int iVar);
        int& getIVar();
        void setIVar(int ivar);
};
#endif
 
Et le fichier .cpp :
#include "truc.h"
Truc::Truc(int iVar) : m_iVar(iVar) {

}
int& Truc::getIVar() {
       return m_iVar;
}
void Truc::setIVar(int i) {
    m_iVar = i;
}
 

Et la main :
#include <ctime>
#include <random>
#include "mythread.h"
int main () {
    srand(time(NULL));
    MyThread myThread;
    myThread.start();
    return 0;
}
 

Le débugueur me dis ça :

#0 6898174C sf::Mutex::Mutex() () (D:\Projets-c++\TestThreadMutex\bin\Debug\sfml-system-2.dll:??)
#1 004017DD __static_initialization_and_destruction_0(__initialize_p=1, __priority=65535) (D:/Projets-c++/TestThreadMutex/globals.h:3)
#2 00401805 _GLOBAL__sub_I__ZN8MyThreadC2Ev() (D:\Projets-c++\TestThreadMutex\mythread.cpp:14)
#3 0040261A __do_global_ctors () (??:??)
#4 40000060 ?? () (??:??)
#5 0000003D ?? () (??:??)
#6 00962F20 ?? () (??:??)
#7 762333AA KERNEL32!BaseCleanupAppcompatCacheSupport() (C:\Windows\syswow64\kernel32.dll:??)
#8 0028FFD4 ?? () (??:??)
#9 76FB9EF2 ntdll!RtlpNtSetValueKey() (C:\Windows\system32\ntdll.dll:??)
#10 7EFDE000 ?? () (??:??)
#11 76FB9EC5 ntdll!RtlpNtSetValueKey() (C:\Windows\system32\ntdll.dll:??)
#12 004014E0 WinMainCRTStartup () (??:??)
#13 7EFDE000 ?? () (??:??)
#14 ?? ?? () (??:??)

PS : j'ai le même soucis avec les thread du c++11 donc le bug ne vient pas de la SFML mais de je ne sais pas trop ou, une librairie de plus bas niveau sans doute.
« Modifié: Juillet 31, 2013, 05:36:09 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Ca plante avant le main(), dans le constructeur de sf::Mutex. Tu n'aurais aucun code autre que la déclaration du mutex, ça planterait aussi... Tu ne t'es pas rendu compte de ça en examinant la pile d'appels ?

Ensuite ce code ne contient qu'un thread en plus du thread principal, et le thread principal ne fait qu'attendre sur le thread. Donc au final ce code est mono-threadé. Il n'y a donc aucun accès concurrent et le mutex ne sert strictement à rien.

Bref, c'est pas encore très concluant ;)
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Oui heu, j'essaye déja de faire fonctionner un thread tout seul, sans mutex, mais, là, ça ne fonctionne pas...
Même si j'enlève le mutex ça ne marche pas, ça crash dans la méthode launch..., donc, je ne sais pas vraiment faire plus pour le moment tant que je n'aurai pas résolu ce problème.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Ca donne quoi exactement sans le mutex ? (pile d'appels)
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ca (ça plante dans la fonction wait.)

#0 689835B1 sf::priv::ThreadImpl::wait() () (D:\Projets-c++\TestThreadMutex\bin\Debug\sfml-system-2.dll:??)
#1 68982B21 sf::Thread::wait() () (D:\Projets-c++\TestThreadMutex\bin\Debug\sfml-system-2.dll:??)
#2 68982B57 sf::Thread::launch() () (D:\Projets-c++\TestThreadMutex\bin\Debug\sfml-system-2.dll:??)
#3 00401702 MyThread::start(this=0x28feb4) (D:\Projets-c++\TestThreadMutex\mythread.cpp:7)
#4 0040165D main() (D:\Projets-c++\TestThreadMutex\main.cpp:7)

Mais pourquoi il faut le fichier .dll hors que je link la librairie en statique ?
« Modifié: Juillet 31, 2013, 07:19:19 pm par Lolilolight »

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bon j'ai trouvé en fait c'est libsfml-system-s.a et pas libsfml-system.a qu'il fallait link, d'habitude c'est des .lib pour le linkage dynamique pour ça que j'ai fait la faute, je vais essayer avec plusieurs thread et le mutex maintenant pour voir.

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bon mon code n'est pas encore assez complexe pour reproduire le problème, en fait j'ai un constructeur (classe SrkServer) qui instancie un thread.
Ensuite, dans ce constructeur, je charge la map et je l'ajoute au world.

Ensuite, toujours dans ce même constructeur, j'appelle un 2 ème constructeur (classe SrkChannel.) qui instancie un autre thread. (Donc j'ai un thread qui lis les données reçue par le client, ensuite, un autre thread par canal (le serveur contient plusieurs canaux.)) qui se charge de mettre à jour les entités et envoyer tout au client, j'ai besoin d'un pointeur sur la map dans ma classe SrkChannel pour pouvoir avoir accès à certaines données, pour la mise à jour, comme par exemple, les collisions.

Dans le constructeur ou j'instancie le 1er thread, je charge la map, je l'ajoute à la classe World qui ne contient rien d'autre qu'un std::vector de Map avec toute les maps du jeux, ensuite, j'appelle le constructeur du second thread dans lequel je récupère la map pour que le deuxième thread puisse y avoir accès aussi.

Mais ça crash, je suis obligé d'appelé le constructeur qui instancie le second thread, dans la fonction qui démarre le 1er thread, et non pas dans le constructeur qui instancie le 1er thread, sinon, ça plante.

Je vais essayer de reproduire le crash, avec un code plus simple. (Donc avec un constructeur qui instancie un thread qui fait appel à un constructeur qui instancie un autre thread.)



Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Citer
Bon j'ai trouvé en fait c'est libsfml-system-s.a et pas libsfml-system.a qu'il fallait link, d'habitude c'est des .lib pour le linkage dynamique
Rien à voir, .a c'est gcc et .lib c'est Visual C++.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ha, ok!

lefreut

  • Newbie
  • *
  • Messages: 14
    • Voir le profil
PS : j'ai le même soucis avec les thread du c++11 donc le bug ne vient pas de la SFML mais de je ne sais pas trop ou, une librairie de plus bas niveau sans doute.

Plutôt que de rejeter la faute sur la SFML puis sur la bibliothèque standard C++, ça ne te parait pas plus plausible que le bug soit dans ton code  ;)

Faire du multithreading est très complexe, à moins de vraiment en avoir besoin et de savoir ce que tu fais, je te conseille de t'en passer.

Au passage, les variables globales et les fonctions statiques (dont ton exemple est rempli) ne font pas bon ménage avec les threads.

cobra.one

  • Newbie
  • *
  • Messages: 26
    • Voir le profil
+1000 avec lefreut...

Le "bug" vient à 99,99% de ton code, et encore, en étant gentil ! En plus des conseils de lefreut, j'ajouterai que les accès concurrents mal gérés peuvent causer des crashs dont l'origine est parfois TRES difficile à identifier, pour lesquels même les debuggers se plantent lamentablement. Je parle en connaissance de cause. Moralité : éviter autant que possible les threads, sinon essayer de limiter au maximum les effets de bord en limitant les interactions entre threads, et en rendant le moindre booléen partagé atomique, et les mutex seulement quand c'est absolument nécessaire et en y réfléchissant bien afin d'éviter les interblocages et autres joyeusetés...

Sinon c'est l'usine à gaz assurée, avec un fonctionnement disons... aléatoire.

Enfin dernière chose : si ton code est sensé être portable, teste le bien sur toutes les plateformes cibles au fur et à mesure du développement, parce que rien que concernant la partie concurrente de ton programme, ça va marcher sur le système X, et sur Y pouf... plantage. Idem concernant les "stress tests"... ne pas les oublier parce que tes threads peuvent tourner nickel pendant 2h et tout planter à 2h01, ou bien fonctionner 8h sans souci un jour, et le lendemain planter toutes les 3 minutes.