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 40359 fois)

0 Membres et 1 Invité sur ce sujet

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Salut,
oui je sais cela d'ailleurs se serait lourd que le serveur plante toutes les 2h.
Pour le moment, je me suis contenté de instancier créer le second thread dans la méthode start du 1er thread, au lieu de créer le second thread dans le constructeur du 1er thread, et ça ne crash plus, mais je suis pas sûr que c'est une solution qui va vraiment marcher.

Bref je pense que je vais utiliser une variable booléenne plutôt qu'un mutex, ça ira surement mieux.

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Voilà j'ai réussi à reproduire le crash, ça crash à un moment aléatoire à l'exécution :

Classe bidule : fichier bidule.h

#ifndef BIDULE
#define BIDULE
#include "truc.h"
#include <vector>
class Bidule {
public :
       static Truc* getTruc (std::string name);
       static void addTruc (Truc *truc);
private :
       static std::vector<Truc*> trucs;
};
#endif // BIDULE
 

fichier  bidule.cpp

#include "bidule.h"
using namespace std;
vector<Truc*> Bidule::trucs = vector<Truc*>();
void Bidule::addTruc (Truc * truc) {
    trucs.push_back(truc);
}
Truc* Bidule::getTruc (string name) {
    for (int i = 0; i < trucs.size(); i++)
        if (trucs[i]->getName() == name)
            return trucs[i];
    return nullptr;
}
 

Classe Truc : fichier.h

#ifndef TRUC_H
#define TRUC_H
#include <string>
class Truc {
private:
        int m_iVar;
        std::string m_sName;
public:
        Truc (std::string sName, int iVar);
        int& getIVar();
        void setIVar(int ivar);
        std::string getName();
};
#endif
 

fichier.cpp

#include "truc.h"
using namespace std;
Truc::Truc(string sName, int iVar) : m_iVar(iVar), m_sName(sName) {

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

Classe MyThread : fichier.h

#ifndef MY_THREAD
#define MY_THREAD
#include <SFML/System.hpp>
#include "truc.h"
#include "bidule.h"
#include "gbls.h"
#include <random>
#include <iostream>
#include "mySecondThread.h"
class MyThread {
public :
    MyThread ();
    void start ();
private :
    void run ();
    Truc* truc;
    sf::Thread m_thread;
    MySecondThread m_secondThread;
};
#endif // MY_THREAD
 

fichier.cpp

#include "mythread.h"
using namespace std;
MyThread::MyThread () : m_thread (&MyThread::run, this) {
    Truc* t = new Truc("TRUC", 5);
    Bidule::addTruc(t);
    truc = Bidule::getTruc("TRUC");
}
void MyThread::start () {
    //m_secondThread = new MySecondThread(20);
    m_thread.launch();
    m_secondThread.start();
}
void MyThread::run () {
    while (true) {
        globalMutex.lock();
        int iVar = rand() % 100;
        truc->setIVar(iVar);
        cout<<"iVar : "<<truc->getIVar()<<endl;
        globalMutex.unlock();
    }
}
 

Classe MySecondThread : fichier.h

#ifndef MY_SECOND_THREAD
#define MY_SECOND_THREAD
class MySecondThread {
public :
    MySecondThread ();
    void start ();
private :
    void run ();
    Truc* truc;
    sf::Thread m_thread;
};
#endif // MY_SECOND_THREAD
 

fichier.cpp

#include "mythread.h"
using namespace std;

MySecondThread::MySecondThread () : m_thread (&MySecondThread::run, this) {
    truc = Bidule::getTruc("TRUC");
}
void MySecondThread::start () {
    m_thread.launch();
}
void MySecondThread::run () {
    while (true) {
        globalMutex.lock();
        int iVar = rand() % 100;
        truc->setIVar(iVar);
        cout<<"iVar : "<<truc->getIVar()<<endl;
        globalMutex.unlock();
    }
}
 

Mon mutex :

#ifndef GBLS_H
#define GBLS_H
#include <SFML/System.hpp>
static sf::Mutex globalMutex;
#endif // GLOBAL_H
 

Et le main :

#include <ctime>
#include <random>
#include "mythread.h"
#include <iostream>
using namespace std;
int main () {
    srand(time(NULL));
    MyThread myThread;
    myThread.start();
    Truc *truc = Bidule::getTruc("TRUC");
    while (true) {
        globalMutex.lock();
        int iVar = rand() % 100;
        truc->setIVar(iVar);
        cout<<"iVar : "<<truc->getIVar()<<endl;
        globalMutex.unlock();
    }
    return 0;
}
 

Voilà, si j'utilise un pointeur sur MySecondThread dans la classe MyThread et que je crée le thread plus tard ça ne crash plus mais je ne sais pas si c'est une bonne solution pour régler le problème car ça pourrait planter sur un autre OS ou bien planter au bout de 2h.


PS : ça crash aussi à différents moment si je lance le débugueur ou pas : si je lance le débugueur ça crash tout de suite, sinon, si je le lance normalement ça crash plus tard.

PS 2 : voilà donc comment faire du code qui crash de manière aléatoire.  :D
« Modifié: Août 05, 2013, 10:06:30 am par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
C'est normal que ça crashe, et ça n'a rien à voir avec le multi-threading : m_secondThread est construit avant l'appel à Bidule::addTruc, du coup il récupère puis utilise un pointeur nul.

Pense à utiliser ton debugger, un pointeur nul c'est facile à repérer.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ha oui juste!

Pour ça que ça plantais dans mon plus gros projet alors, sauf que là il ne m'indiquait pas de pointeur null le débugueur...

Bref j'ai enfin compris pourquoi ça buguais et que je devais créer mon second thread plus tard. (Car sinon il récupèrais un pointeur null.)


cobra.one

  • Newbie
  • *
  • Messages: 26
    • Voir le profil
Bref je pense que je vais utiliser une variable booléenne plutôt qu'un mutex, ça ira surement mieux.

 :o

Un booléen, de base, n'est ni atomique, ni thread-safe... donc je ne vois pas trop comment il peut remplacer un mutex...

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Bah tu mets le booléen à false quand tu veux que ton thread bloque l'accès et à true quand ton thread à fini. (Mais cette variable booléenne doit être unique, donc statique, et accessible partout, donc, globale.)

Mais il reste un problème, les autres threads doient être mis en attente en attendant que le thread courant soit déverouillé et ça je vois pas comment faire, le verrou c'est simple c'est juste une variable booléenne mais pour le reste je ne sais pas comment ça fonctionne.

Je pensais faire ça parce que même en créant mon objet "secondThread" plus tard en l'allouant dynamiquement, pour que le pointeur sur mon objet truc ne soit pas null, ça plante quand même dans mon plus gros code, quand y'a plus de risque d'accès concurrents, d'ailleurs je vais faire un stress test avec ce petit bout de code si j'alloue mon objet seconde thread dynamiquement dans le constructeur pour voir...
Parce que y'a un truc que je ne comprend pas je fais exacement pareil dans les 2 codes, avec l'un ça fonctionne, avec l'autre ça plante.

Et atomique c'est le contraire de composé donc un booléen est bien atomique vu qu'il n'est pas composé de plusieurs autres variables. (Tout comme tout les autres type primitifs du c++, int, long, short, char, etc..)
Et je crois pas que le booléen ai besoin d'être thread safe mais il me semble qu'il existe un mot clé en c++ pour rendre une variable thread safe, ou je me trompe ?

PS : par contre le endl ne marche pas toujours dans la console, j'ai bien mis un endl dans les 3 threads, ça, c'est bizarre.
« Modifié: Août 06, 2013, 11:43:06 am par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Tu devrais vraiment potasser quelques articles / tutoriels concernant les threads et les accès concurrents.

Le standard C++ ne spécifie pas qu'une lecture ou écriture de booléen soit atomique. Si c'est le cas, alors ce sera uniquement parce que la plateforme particulière sur laquelle tu exécutes ton code le garantit. Du coup il vaut mieux ne pas en faire l'hypothèse par défaut, et considérer que seul std::atomic<T> est atomique.

Du coup, si ton booléen protège ton code, qui protège ton booléen ? Tu tournes en rond avec ce genre de raisonnement, il y a un moment où il faut forcément introduire une primitive de synchronisation (mutex ou autre).

Et quant à refaire un mutex from scratch... tu crois vraiment que ce sont les mutexs systèmes / SFML qui sont buggés et pas ton code ? Sérieusement, si je peux te donner un conseil, arrête de penser comme ça et remets-toi un peu en question. Ici on veut bien t'aider mais il faut que tu y mettes un peu de bonne volonté. Arrête-toi un peu, potasse de la doc, comprends les choses et ensuite on pourra tranquillement se pencher sur les éventuels problèmes qu'il te restera.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
re
« Réponse #37 le: Août 06, 2013, 12:32:38 pm »
Ouiais, bref, c'est bizarre, le débugueur ne me donne pas beaucoup d'infos :

#0 ?? CellMap::getCenter (this=0xabababab) (D:\Projets-c++\SorrokSrv\world\mapCell.cpp:91)
#1 0043D648 GridMap::getPath(this=0x2b400e0, startPos=..., finalPos=...) (D:\Projets-c++\SorrokSrv\world\gridMap.cpp:425)
#2 0042D2E4 SrkChannel::run(this=0x2be4a78) (D:\Projets-c++\SorrokSrv\NetworkEngine\srkchannel.cpp:76)
#3 00E9A3C8 ?? () (??:??)
#4 ?? ?? () (??:??)

Et ça ne plante que quand je lance le débugueur, quand je le lance normalement ça ne plante pas.

Donc le problème vient peut être ailleurs que dans les mutex et les threads, en fait, je sais pas vraiment d'ou ça vient. :/

Peut être d'ici ?
#include "boundingPolygon.h"
BoundingPolygon::BoundingPolygon () : center(0, 0) {
}
BoundingPolygon::BoundingPolygon (const BoundingPolygon &bp) {
    for (unsigned int i = 0; i < points.size(); i++)
        delete points[i];
    points.clear();
    for (unsigned int i = 0; i < bp.points.size(); i++)
        addPoint(new Vec2f(*bp.points[i]));
    computeCenter();
}
void BoundingPolygon::addPoint(Vec2f *point) {
    points.push_back(point);
    computeCenter();
}
bool BoundingPolygon::isPointInside(Vec2f &p) {

    /*On calcule le nombre d'intersections avec une demi-droite
    *partant du point p vers le haut, et les segments du polygône.
    *Si le nombre d'intersections est impair, alors le point est dans le polygône!
    *          |
    *          /\
    *         /| \
    *        / |  \
    *        \ .  /
    *         \  /
    *          \/
    */

    int nbIntersections = 0;
    Vec2f i;
    i.x = 10000 + rand() % 100;
    i.y = 10000 + rand() % 100;

    for (unsigned int n = 0; n < points.size() ; n++) {
        Vec2f a = *points[n];
        Vec2f b;
        if (n == points.size() - 1)
            b = *points[0];
        else
            b = *points[n + 1];
        Segment s1 (a, b);
        Segment s2 (i, p);
        int iseg = s1.intersects(s2);
        if (iseg == -1)
            return isPointInside(p);
        nbIntersections += iseg;
    }
   //Si le nombre d'intersections est impaire alors le point est dans le polygône.
   return nbIntersections % 2 != 0;
}
unsigned int BoundingPolygon::nbPoints () {
    return points.size();
}
bool BoundingPolygon::intersects (BoundingRectangle &br) {
    Vec2f pts[4];
    pts[0] = Vec2f (br.getPosition().x, br.getPosition().y);
    pts[1] = Vec2f (br.getPosition().x + br.getWidth(), br.getPosition().y);
    pts[2] = Vec2f (br.getPosition().x + br.getWidth(), br.getPosition().y + br.getHeight());
    pts[3] = Vec2f (br.getPosition().x, br.getPosition().y + br.getHeight());

    for (int i = 0; i < 4; i++) {
       Vec2f v1 = pts[i];
       Vec2f v2;
       if (i == 3)
           v2 = pts[0];
       else
           v2 = pts[i + 1];
       Segment s1 (v1, v2);
       for (unsigned int j = 0; j < points.size(); j++) {
            Vec2f v3 = *points[j];
            Vec2f v4;
            if (j == points.size() - 1)
                v4 = *points[0];
            else
                v4 = *points[j+1];
            Segment s2 (v3, v4);

            if (s1.intersects(s2) > 0.f) {

                return true;
            }
       }
    }
    return false;
}
void BoundingPolygon::computeCenter () {
    Vec2f sum(0.f, 0.f);
    for (unsigned int i = 0; i < points.size(); i++) {
        if (points[i] != NULL)
            sum += *points[i];
    }
    center = sum / points.size();

}
BoundingPolygon BoundingPolygon::operator= (const BoundingPolygon &bp) {
    for (unsigned int i = 0; i < points.size(); i++)
        delete points[i];
    points.clear();
    for (unsigned int i = 0; i < bp.points.size(); i++)
        addPoint(new Vec2f(*bp.points[i]));
    computeCenter();
    return *this;
}
BoundingPolygon::~BoundingPolygon () {
    for (unsigned int i = 0; i < points.size(); i++) {
        delete points[i];

    }
    points.clear();

}
bool BoundingPolygon::operator== (const BoundingPolygon &bp) {
    if(points.size() != bp.points.size())
        return false;
    for (unsigned int i = 0; i < points.size(); i++) {
        if (!(*points[i] == *bp.points[i])) {
            return false;
        }
    }
    return true;
}
Vec2f BoundingPolygon::getPoint(unsigned int index) {
    if (index >= 0 && index < points.size())
        return *points[index];
    return Vec2f (0, 0);
}
Vec2f BoundingPolygon::getCenter () {
    return center;
}
 

#include "mapCell.h"
using namespace std;
CellMap::CellMap (BoundingPolygon *bp, Vec2f coords) {
    entityInside = vector<vector<Entity*> >(NB_LAYERS);
    poly = bp;
    center = poly->getCenter();
    this->coords = coords;
    passable = true;
    stateChanged = false;
    traveled = false;
}
Vec2f CellMap::getCenter () {
    return center;
}
 

Mais je sèche je vois pas du tout ce qui pourrait planter avec la variable center.
« Modifié: Août 06, 2013, 12:35:46 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Le debugger te donne pas mal d'infos : il te dit que dans GridMap::getPath tu appelles la fonction getCenter sur une instance de CellMap non-initialisée.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ha, mais ou t'a vu qu'il a marqué ça ?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Citer
#0 ??   CellMap::getCenter (this=0xabababab) (D:\Projets-c++\SorrokSrv\world\mapCell.cpp:91)
Ici on voit que le crash est survenu directement dans cette fonction. La valeur 0xabababab pour "this" est une valeur spéciale, donc elle a une signification spéciale. Une rapide recherche montre que pour gcc, elle signifie "espace mémoire non initialisé".

Ensuite en remontant d'un cran dans la pile d'appels, on voit que l'appel foireux a été fait dans la fonction GridMap::getPath, et que celle-ci possède par contre un "this" a priori valide (parfois les "this" foireux s'imbriquent sur plusieurs niveaux, avant que ça crashe).
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Ha je ne savais pas que cette valeur était une valeur spéciale pour "this".

C'est encore différent de la valeur null dans ce cas ? :/

Et comment puis je repéré si une cellMap n'est pas initialisée dans le programme ? (Car mes CellMap sont dont un std::vector, et c'est possible que mon std::vector contienne des cases vide.)

Je pensais que c'était automatiquement initialisé à null dans ce cas mais apparemment non. :/

« Modifié: Août 06, 2013, 01:55:33 pm par Lolilolight »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Citer
C'est encore différent de la valeur null dans ce cas ?
"null" fait parti du langage, c'est le pointeur qui vaut zéro.

Là c'est différent : c'est le compilateur qui te donne un coup de pouce en debug, au lieu de laisser une zone mémoire avec du contenu aléatoire, il y met quelque chose qui te permettra d'avoir des informations en cas de bug. C'est complètement interne au compilateur comme fonctionnalité, cela n'a rien à voir avec le langage.

Typiquement, tu peux avoir des valeurs spéciales pour :
- de la mémoire non allouée
- de la mémoire désallouée
- de la mémoire en dehors de la pile
- etc.

Citer
Et comment puis je repéré si une cellMap n'est pas initialisée dans le programme
Pour ça il faut inspecter le code source correspondant.

Tu peux aussi utiliser des outils dédiés, comme valgrind.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Erf valgrind ne marche pas sous windows. :/

Pour une raison que j'ignore, mes CellMap ne sont pas bien initialisée dans mon thread. (Lorsque j'initialise le thread, dans le constructeur de mon autre thread, si j'initialise ailleurs ça marche.)

Et pourtant j'initialise tout en même temps, la map, les cellMaps, etc...

La map est bien initialisée vu que le pointeur est valide mais les CellMap ne le sont pas.

Faudrait que je trouve un outil du genre mais pour windows, ou alors que je passe sous linux pour trouver le problème. (Ca ne plantera peut être pas sous linux xd.)
« Modifié: Août 06, 2013, 02:45:44 pm par Lolilolight »

cobra.one

  • Newbie
  • *
  • Messages: 26
    • Voir le profil
Re : re
« Réponse #44 le: Août 06, 2013, 11:01:52 pm »
Et ça ne plante que quand je lance le débugueur, quand je le lance normalement ça ne plante pas.

[...]

Pour une raison que j'ignore, mes CellMap ne sont pas bien initialisée dans mon thread. (Lorsque j'initialise le thread, dans le constructeur de mon autre thread, si j'initialise ailleurs ça marche.)


Je n'ai pas suffisamment d'éléments pour être catégorique, mais ce genre de crash non systématique ressemble fortement à un problème dû à une mauvaise synchronisation de threads... Attention au fait qu'il n'y a aucune garantie quant à l'ordre d'exécution des threads.

Il ne faut donc pas s'attendre à ce que le code s'exécute tel qu'il est écrit. Ca n'est pas parce qu'un thread T1 est lancé avant un thread T2 dans le code qu'il y a la garantie du système que T1 commencera bien à s'exécuter avant T2. C'est "possible", mais pas "certain". Avec de probables conséquences sur les ressources créées par un thread et accédées par un autre...

 

anything