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

Auteur Sujet: Problème partage de variables statiques entre plusieurs thread sous linux.  (Lu 6601 fois)

0 Membres et 1 Invité sur ce sujet

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Salut alors je m'explique :
J'ai une classe statique World qui contient toutes les maps de mon jeux (côté serveur.), donc un static std::vector<Map>, je charge les maps à l'initialisation de ma classe serveur, donc, dans le thread principal.
Pour le serveur j'utilise un thread secondaire pour la réception des paquets venant du client, dans ce thread je récupère la map (de la classe world) sur laquelle est le client à partir du thread secondaire donc...

Sur windows ça marche très bien mais sur mon ubuntu, lorsque je veux récupéré la map sur laquelle est le client à partir de mon thread secondaire, je me retrouve avec un vector vide.
Je ne sais pas pour quelle raison mais on dirait que mon vecteur n'a pas été partagé entre mes deux threads. :/

Voici quelques bout de code pour plus de clarté :
Le constructeur de ma classe serveur, il charge la map, ceci se fait dans le thread principal.

SrkServer::SrkServer(int nbChannels) : thread(&SrkServer::run, this)
{
    UpsMapLoader loader("Maps/test.upsmap");
    Map *map = new Map();
    if (loader.createMap(*map,map->getLightManager(), map->getImageManager())) {
        World::addMap(map);
    } else {
        delete map;
    }
    for (int i = 0; i < nbChannels; i++) {
        SrkChannel *channel = new SrkChannel(*this, "Channel "+conversionIntString(i+1));
        channels.push_back(channel);
    }
    running = false;
}
 

Ma boucle qui réceptionne les messages du client ceci se fait dans mon thread secondaire :

void SrkServer::run () {
    running = true;
    while (running) {

        if (selector.wait()) {

            if (selector.isReady(listener)) {

                TcpSocket *client = new TcpSocket();
                if (listener.accept(*client) == Socket::Done) {
                    selector.add(*client);
                    Network::addUser(*client);
                }
            } else {
                vector<TcpSocket*>::iterator it;
                vector<TcpSocket*> clients = Network::getClients();
                for (it = clients.begin(); it != clients.end(); it++) {
                    TcpSocket &client = **it;
                    if (selector.isReady(client)) {
                        Packet* packet;

                        bool pbKeyRsaSend = Network::hasPbKeyRsa(client);
                        bool pbKeySend = Network::hasPbKey(client);
                        bool authentified = Network::isAuthentified(client);
                        if (pbKeySend && pbKeyRsaSend) {
                            packet = new SymEncPacket ();
                        } else if (!pbKeySend && pbKeyRsaSend) {
                            packet = new EncryptedPacket ();
                        } else {
                            packet = new Packet();
                        }
                        if (client.receive(*packet) == Socket::Done) {
                            string request;
                            (*packet)>>request;
                            Network::addRequest (client, request);
                            if (pbKeySend && pbKeyRsaSend && authentified) {
                                Network::processLastResquest();
                            } else if (pbKeySend && pbKeyRsaSend && !authentified) {
                                Network::authentify(client);
                            } else if (!pbKeyRsaSend && request == "GetPbKeyRsa") {
                                Network::sendPbKeyRsa();
                            } else if (!pbKeySend && request == "GetPbKey") {
                                Network::sendPbKey();
                            } else {
                                selector.remove(client);
                                Network::removeUser(client);
                            }
                        } else {
                            selector.remove(client);
                            Network::removeUser(client);
                            for (unsigned int i = 0; i < channels.size(); i++){
                                channels[i]->removeClient(client);
                            }
                        }
                    }
                }
            }
        }
    }
}
 
Ma classe world, classe statique qui contient toutes les maps, j'ai fait une classe statique ainsi je ne dois pas à chaque fois passé la variable world à toute les classes qui utilisent la classe map, et puis comme il n'y a que une seul monde..., j'ai fait aussi pareil avec une classe Network qui se charge de traiter toutes les requêtes provenant du client.
#include "world.h"
using namespace std;
vector<Map*> World::maps = vector<Map*> ();
void World::addMap (Map *map) {
    maps.push_back(map);
}


Map* World::getMap (string mapName) {

    vector<Map*>::iterator it;
    cout<<"Map size : "<<maps.size()<<endl;
    for (it = maps.begin(); it != maps.end(); it++) {
        if ((*it)->getName() == mapName) {
            return *it;
        }
    }
    return NULL;
}


World::~World() {

    for (unsigned int i = 0; i < maps.size(); i++)
        delete maps[i];
    maps.clear();
}

 

Voilà et lorsque j'appelle Network::processLastRequest(), à partir de mon second thread mon vecteur maps de ma classe statique World est vide (je n'ai ce problème que sur linux.),  donc quand je veux récupérer la map ça me retourne une adresse null et ça plante. :/

void Network::loadMap(TcpSocket &client, string mapName) {

    Map *map = World::getMap(mapName);
    cout<<"Map : "<<map<<endl;
    loadPathsImages(client, map);
    loadTiles(client, map);
    loadAnims(client, map);
    loadLights(client, map);
    loadWalls(client, map);
}
 

PS : processLastRequest appelle loadMap.
PS 2 : je n'ai ce problème apparemment que avec les variables statique, toutes les variables qui ne sont pas statiques semblent être partagées entre les différents threads.
« Modifié: Mars 06, 2013, 05:57:49 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Et tu as vérifié que ton tableau de maps n'était pas vide juste parce que createMap foirait systématiquement ?

Tu dis que seules les variables statiques provoquent ce problème, ça signifie que tu en as testé d'autres ?

Tu devrais réduire le problème à quelque chose de minimal, là il y a trop de choses qui entrent en compte. Par exemple, teste un nouveau projet avec juste une variable statique et deux threads (en d'autres termes, uniquement ce que tu suspecte). Il y a de fortes chances pour que ça marche, et que tu te rendes compte que les variables statiques ne sont pas la cause du problème.

Bref, prend ton debugger et creuse un peu, c'est pas le genre de problème qui se résout magiquement sur un forum, malheureusement :-\
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Citer
Et tu as vérifié que ton tableau de maps n'était pas vide juste parce que createMap foirait systématiquement ?
createMap marche très bien.

Citer
Tu dis que seules les variables statiques provoquent ce problème, ça signifie que tu en as testé d'autres ?

J'ai oublier de préciser : les variables statique de type "std::vector", j'ai testé avec juste une variable statique de type Map et là ça marche. :o
#include "world.h"
using namespace std;

Map* World::map = NULL;
void World::addMap (Map *map) {
      World::map = map;    
}


Map* World::getMap (string mapName) {
    return map;
}
 

Citer
Tu devrais réduire le problème à quelque chose de minimal, là il y a trop de choses qui entrent en compte. Par exemple, teste un nouveau projet avec juste une variable statique et deux threads (en d'autres termes, uniquement ce que tu suspecte). Il y a de fortes chances pour que ça marche, et que tu te rendes compte que les variables statiques ne sont pas la cause du problème.

Oui je vais essayer avec un code minimal pour voir ce que ça donne.

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Re : Reproduction du bug avec un code minimal
« Réponse #3 le: Mars 07, 2013, 09:03:17 am »
Voila qui est fait, il ne m'affiche pas coucou! sur la fenêtre du terminal, donc dans la fonction run de mon thread mon vecteur est bien vide. :/

1)La classe avec le thread :
#ifndef CLASS_THREAD
#define CLASS_THREAD
#include <SFML/System.hpp>
#include "staticClass.h"
#include <iostream>
class ClassWithThread {
    public :
        ClassWithThread();
        void startThread ();
        void stopThread ();
        ~ClassWithThread();
    private :
        void run();
        bool running;
        sf::Thread mThread;
};
#endif
 
#include "classThread.h"
using namespace sf;
using namespace std;
ClassWithThread::ClassWithThread () : mThread (&ClassWithThread::run, this) {

    running = false;
    StaticClass::addText("coucou!");
}
void ClassWithThread::startThread() {
    running = true;
    mThread.launch();
}
void ClassWithThread::run() {
    while (running) {
        vector<string> texts = StaticClass::getTexts();
        for (unsigned int i = 0; i < texts.size(); i++) {
            cout<<texts[i]<<endl;
        }
    }
}
void ClassWithThread::stopThread() {
    running = false;
}
ClassWithThread::~ClassWithThread() {
    stopThread();
}

 
2)La classe avec mon vecteur statique :
#ifndef STATIC_CLASS
#define STATIC_CLASS
#include <vector>
#include <string>
class StaticClass {
    public :
        static void addText (std::string text);
        static std::vector<std::string> getTexts ();
    private :
        static std::vector<std::string> texts;
};
#endif
 
#include "staticClass.h"
using namespace std;
vector<string> StaticClass::texts = vector<string> ();
void StaticClass::addText(string text) {
    texts.push_back(text);
}
vector<string> StaticClass::getTexts () {
    return texts;
}
 
3)Mon main :
#include <SFML/System.hpp>
#include "classThread.h"
int main () {
    ClassWithThread classWithThread;
    classWithThread.startThread();
    return EXIT_SUCCESS;
}
 


« Modifié: Mars 07, 2013, 09:05:27 am par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Tu devrais déjà le faire avec un entier, au lieu d'un tableau de chaînes. Et pas besoin de classe pour encapsuler le thread, fais le directement dans le main().

struct Static
{
    static int x;
};
int Static::x = 0;

void f()
{
    std::cout << Static::x << std::endl;
}

int main()
{
    Static::x = 10;

    sf::Thread thread(&f);
    thread.launch();

    return 0;
}

Si ça marche, remplace le int par un vector<int>, et affiche simplement sa taille dans le thread. Bref, tu as saisi le truc, le but c'est vraiment d'y aller par petites étapes, de sorte qu'au moment où ça bug, tu saches très précisément pourquoi.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ok je vais essayer de faire ça et voir ce que ça donne.

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bon j'ai des problèmes bizarre avec les threads de la SFML2 sous ubuntu, j'ai essayer en supprimant ma classe, ça fonctionne, puis je retest avec une classe, ça fonctionne hors que avant ça ne fonctionnais pas, puis, lorsque j'utilise un pointeur sur ma classe qui encapsule le Thread, ça ne marche de nouveau plus.
 ClassWithThread *cwt = new ClassWithThread();
 cwt->startThread();
 
Ca ne m'affiche plus rien dans ce cas.
Bref...., donc en gros, parfois ça fonctionne, parfois ça ne fonctionne plus.
Sinon petite question, les variables statique sont initialisées quand ? (Avant ou après les constructeurs?)
« Modifié: Mars 08, 2013, 10:48:04 am par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Les variables statiques s'initialisent au moment où tu les initialises :

int Static::x = 0;

Comme c'est dans la portée globale, c'est fait avant d'entrer dans la fonction main().

Si tu veux qu'on continue à avancer, il faut que tu poses ton code minimal à chaque fois que ça recoince.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Mon code, c'est le même que celui que j'ai posté dans ce message-ci à part que dans le main j'utilise un pointeur et non une variable simple :
Voila qui est fait, il ne m'affiche pas coucou! sur la fenêtre du terminal, donc dans la fonction run de mon thread mon vecteur est bien vide. :/

1)La classe avec le thread :
#ifndef CLASS_THREAD
#define CLASS_THREAD
#include <SFML/System.hpp>
#include "staticClass.h"
#include <iostream>
class ClassWithThread {
    public :
        ClassWithThread();
        void startThread ();
        void stopThread ();
        ~ClassWithThread();
    private :
        void run();
        bool running;
        sf::Thread mThread;
};
#endif
 
#include "classThread.h"
using namespace sf;
using namespace std;
ClassWithThread::ClassWithThread () : mThread (&ClassWithThread::run, this) {

    running = false;
    StaticClass::addText("coucou!");
}
void ClassWithThread::startThread() {
    running = true;
    mThread.launch();
}
void ClassWithThread::run() {
    cout<<StaticClass::getTexts().size()<<endl;
    while (running) {
       
    }
}
void ClassWithThread::stopThread() {
    running = false;
}
ClassWithThread::~ClassWithThread() {
    stopThread();
}

 
2)La classe avec mon vecteur statique :
#ifndef STATIC_CLASS
#define STATIC_CLASS
#include <vector>
#include <string>
class StaticClass {
    public :
        static void addText (std::string text);
        static std::vector<std::string> getTexts ();
    private :
        static std::vector<std::string> texts;
};
#endif
 
#include "staticClass.h"
using namespace std;
vector<string> StaticClass::texts = vector<string> ();
void StaticClass::addText(string text) {
    texts.push_back(text);
}
vector<string> StaticClass::getTexts () {
    return texts;
}
 


3)Mon main :
#include <SFML/System.hpp>
#include "classThread.h"
int main () {
    ClassWithThread *cwt = new ClassWithThread();
    classWithThread->startThread();
    return EXIT_SUCCESS;
}
 



J'ai fais exactement la même chose dans mon propres projets, seulement j'ai une nouvelle erreur, lorsque j'essaye de charger la map, non plus dans le constructeur de ma classe SrkServer mais dans la méthode de la classe qui démarre mon thread et j'ai un crash :
Citer
#0 0xb7cf8d8a   pthread_join() (/lib/i386-linux-gnu/libpthread.so.0:??)
#1 0xb7fd67bb   sf::priv::ThreadImpl::wait() () (/usr/local/SFML2/lib/libsfml-system.so.2:??)
#2 0xb7fd5738   sf::Thread::wait() () (/usr/local/SFML2/lib/libsfml-system.so.2:??)
#3 0xb7fd5786   sf::Thread::launch() () (/usr/local/SFML2/lib/libsfml-system.so.2:??)
#4 (   0x08048e5a in ??() (??:??)
#5 0xb7d254d3   __libc_start_main() (/lib/i386-linux-gnu/libc.so.6:??)
#6 (   0x08048f25 in ??() (??:??)
Bref ça m'a pris un peu de temps pour reproduire le bug. :/

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Tu n'arrives vraiment pas à simplifier à l'extrême tes codes minimaux, comme celui que j'ai posté ? Tu as vraiment besoin de l'enrobage avec les classes, les en-têtes et leurs include guards, du vecteur de chaînes et de ses accesseurs ? Là il y a plus d'enrobage que de code qui fait vraiment quelque chose.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bah je fais ça surtout pour mieux m'y retrouver au niveau du code, mais là comme tu dis je pense que je vais devoir faire sans enrobage car sur windows ça marchait bien mais là sous ubuntu ça ne passe pas. :/
Ca doit surement dépendre des compilateurs...

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Re : Problème résolu.
« Réponse #11 le: Mars 08, 2013, 12:26:53 pm »
Bon, bah j'en aurai chier, mais j'ai enfin réussi à trouver une solution qui résous se problème là.
On dirait que, pour des raisons que j'ignore, les initialisations des variables dans la classe sf::Thread ne se font pas de la même manière sous windows et linux, bref, parfois dans la fonction appelée par le thread ma variable n'était pas initialisée, parfois elle l'était.
Du coup, dans mon projet, j'ai une fonction que j'ai appelé startThread pour démarrer mon thread et tout ce que j'avais dans le constructeur je l'ai mis dans cette fonction du coup là mon sf::thread est bien initialisé et dès que je fais une modification d'une variable du thread main,  elle est bien remise à jour dans la fonction de mon autre thread hors que ce n'était pas toujours le cas dans le constructeur de ma classe qui englobais le thread.

Et depuis que j'ai fais ça, ça marche. :)






« Modifié: Mars 08, 2013, 12:28:28 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Moi à ta place je ne serais ni satisfait ni rassuré, car tu n'as pas trouvé la cause de l'erreur, et tu as juste caché la misère avec quelque chose dont tu ne sais même pas pourquoi ça résoud le problème :P

Tu pourrais tester avec un std::thread au lieu d'un sf::Thread, juste pour être sûr.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Oui, je ferai ça quand j'aurai le temps. ^^
Pour le moment j'ai un autre problème mais de toute manière ta manière de gérer les threads me semble un peu bizarre et moi même en effet je n'ai pas très bien compris pourquoi quand je modifie mon vecteur dans le constructeur de ma classe qui englobe le thread, ça ne se met pas à jour dans ma fonction appelée par mon thread hors que quand je le fais dans une méthode autre que mon constructeur ça marche, je trouve que pour une classe qui englobe un thread, l'héritage est mieux surtout pour le partage et la mise à jour des variables entre les différents thread lors de l'initialisation, d'ailleurs, toutes les API font ça : Java, Qt, etc..., mais sinon pour les fonctions n'étant pas englobée dans une classe c'est bien sûr très bien comme ça.
Cependant avec la SFML 2.0 je ne suis plus parvenu à faire de l'héritage avec les sf::Thread comme je le faisais dans la SFML 1.6.
Voilà ce qui me semble le plus bizarre dans tout ça en fait.
Se serait donc bien de pouvoir faire les 2.
« Modifié: Mars 08, 2013, 12:45:00 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
C'est pas bizarre, juste mieux (comment crois-tu que fonctionne std::thread ?). Au lieu de se limiter à une manière d'utiliser la classe, je l'ai rendue beaucoup plus flexible.

Il faut bien se rendre compte que l'héritage n'apporte rien, encapsuler la fonction threadée dans une classe est toujours très simple avec la nouvelle API :

class Thread
{
    Thread() : thread(&Thread::run, this)
    {
    }

    void run()
    {
    }

    sf::Thread thread;
}

... alors que faire le contraire, threader une fonction non-membre avec de l'héritage, est beaucoup plus contraignant.

De plus, cette approche colle beaucoup plus à la philosophie du C++ (cf. std::thread).

Et enfin, cela n'a de toute façon aucun rapport avec ton problème ;)
Laurent Gomila - SFML developer

 

anything