IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Threads & Processus C++ Discussion :

code réentrant pour supprimer dans une liste


Sujet :

Threads & Processus C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    162
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Mai 2002
    Messages : 162
    Points : 88
    Points
    88
    Par défaut code réentrant pour supprimer dans une liste
    Bonjour,

    Je me pose une question sur la possibilité d'un double appel d'une méthode qui supprime un élément dans une liste.

    La situation est la suivante. J'ai des sons qui sont joués par la SDL. Chaque son est lancé par un gestionnaire commun qui garde un pointeur sur le son dans une liste. Quand l'un d'eux se termine, un callback prévient le gestionnaire de son, qui le supprime de sa liste. J'imagine que si un son se termine pendant que je suis en train de supprimer le précédent de la liste, je peux avoir un problème.

    Ci-dessous, la situation en code.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    void gestionnaire::lire( const std::string& n )
    {
      // ici on garde un pointeur sur le son lu
      sample* s = new sample(n);
      m_samples.push_back(s); // std::list<sample*>
      s->lire();
    }
     
    void gestionnaire::sample_fini( sample* s )
    {
      std::list<sample*>::iterator it=m_samples.begin();
      bool ok(false);
     
      while ( !ok && (it!=m_samples.end()) )
        if ( *it == s )
          {
            ok = true;
            // si un autre sample s'est terminé, a appelé cette fonction et en est à la même
            // position que "it" dans sa boucle, il va être déçu après cette suppression.
            m_samples.erase(it);
          }
        else
          ++it;
    }
    J'ajoute qu'il n'y a que la lecture des sons qui utilise un autre thread et pour lesquels je ne contrôle pas l'évolution. Tout le reste du programme est parfaitement séquentiel.

    Comme indiqué dans le commentaire, si deux sons se terminent en même temps, ils vont appeler manager::sample_fini(). Je ne sais pas alors ce qui va se passer. Est-ce que le fait que le programme soit dans un seul thread fait que les deux appels vont se faire séquentiellement, ou est-ce que le fait que les sons soient dans des threads différents fait que les deux appels vont se faire en parallèle ?

    Dans le doute, je suppose que c'est le second cas. Quelles sont alors les solutions pour éviter les problèmes ?

    Je pensais déclarer une variable "boost::mutex fini_mutex;" au début de manager::sample_fini(), mais comme je ne suis pas familier avec la programmation multi-thread, je voudrais avoir confirmation que ça va bien empêcher deux appels parallèles de la méthode (et que c'est une solution correcte pour ce problème).

    Merci

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,
    Citation Envoyé par YéTeeh Voir le message
    Ci-dessous, la situation en code.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    void gestionnaire::lire( const std::string& n )
    {
      // ici on garde un pointeur sur le son lu
      sample* s = new sample(n);
      m_samples.push_back(s); // std::list<sample*>
      s->lire();
    }
     
    void gestionnaire::sample_fini( sample* s )
    {
      std::list<sample*>::iterator it=m_samples.begin();
      bool ok(false);
     
      while ( !ok && (it!=m_samples.end()) )
        if ( *it == s )
          {
            ok = true;
            // si un autre sample s'est terminé, a appelé cette fonction et en est à la même
            // position que "it" dans sa boucle, il va être déçu après cette suppression.
            m_samples.erase(it);
          }
        else
          ++it;
    }
    Fais déjà attention au fait que, si le son n'est pas trouvé dans la liste (parce qu'il a déjà été supprimé ), ton code ne sortira jamais de la boucle...

    De plus, étant donné qu'il s'agit de pointeurs, je *présumes* que ton échantillon de son est créé par allocation dynamique de la mémoire.

    Si tu n'utilise pas les pointeurs intelligents ou si m_sample n'est pas un conteneur de boost::pointer_container, il ne faut pas oublier de libérer la mémoire pour l'échantillon, sous peine d'avoir une fuite mémoire

    Sans la gestion des mutex, le code "idéal" serait sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void gestionnaire::sample_fini( sample* s )
    {
      std::list<sample*>::iterator it=m_samples.begin();
      while ((it!=m_samples.end()) )
        if ( *it == s )
          {
            delete (*it); //inutile si m_samples vient de boost::pointer_container
                          // ou si pointeur intelligent
            m_samples.erase(it);
            return;
          }
        else
          ++it;
    }
    qui pourrait même être écrit sous la forme d'une boucle incrémentale proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for(std::list<sample*>::iterator it=m_samples.begin();it!=m_samples.end();
        ++it)
    {
        if ( *it == s )
        {
            delete (*it); //inutile si m_samples vient de boost::pointer_container
                          // ou si pointeur intelligent
            m_samples.erase(it);
            return;
        }
    }
    J'ajoute qu'il n'y a que la lecture des sons qui utilise un autre thread et pour lesquels je ne contrôle pas l'évolution. Tout le reste du programme est parfaitement séquentiel.

    Comme indiqué dans le commentaire, si deux sons se terminent en même temps, ils vont appeler manager::sample_fini(). Je ne sais pas alors ce qui va se passer. Est-ce que le fait que le programme soit dans un seul thread fait que les deux appels vont se faire séquentiellement, ou est-ce que le fait que les sons soient dans des threads différents fait que les deux appels vont se faire en parallèle ?

    Dans le doute, je suppose que c'est le second cas. Quelles sont alors les solutions pour éviter les problèmes ?

    Je pensais déclarer une variable "boost::mutex fini_mutex;" au début de manager::sample_fini(), mais comme je ne suis pas familier avec la programmation multi-thread, je voudrais avoir confirmation que ça va bien empêcher deux appels parallèles de la méthode (et que c'est une solution correcte pour ce problème).

    Merci
    Vu comme cela, effectivement, il faudrait envisager un mutex...Mais...

    • Ce n'est pas forcément parce qu'un échantillon de son a fini d'être joué qu'il ne devra pas... être rejoué plus tard
    • je présume que tes échantillons de sons sont chargés depuis des fichiers présents sur le disque dur
    • La création et le chargement de ces sons (si mon hypothèse ci-dessus est correcte) demande énormément de temps
    Dés lors, et bien qu'on puisse me rétorquer que, étant dans un environnement multi-threadé et disposant (car cela devient pour ainsi dire la norme) de processeurs multi coeurs, la création et le chargement des sons ne risque *pas vraiment* d'impacter sur les performances, je me demande s'il est réellement opportun de vouloir supprimer ces sons une fois qu'ils sont joués...

    Bien sûr, on peut aussi me dire que tous ces sons vont nécessiter pas mal de mémoire, mais, à une époque où le plus faible système du commerce dispose de 2Gb de mémoire physique, ce n'est pas forcément les quelques Ko d'un échantillon de son qui risquent de saturer le système

    Je me demande donc s'il ne serait pas intéressant de "revoir ta copie" au niveau du gestionnaire de son, de manière à utiliser, par exemple, un conteneur associatif (une std::map ) et à garder en mémoire les sons jusqu'à ce que tu saches qu'ils ne sont plus nécessaire (AKA: jusqu'à la fin du niveau / du plateau ou jusqu'à la fin du jeu).

    Ta fonction lire serait alors modifiée sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void gestionnaire::lire( const std::string& n )
    {
        //on cherche l'échantillon à lire
        std::map<std::string,sample*>::iterator it= m_samples.find(n);
        // s'il n'est pas trouvé, on le charge (et on le rajoute à la map)
        if(it==m_samples.end())
        {
            sample* temp=new sample(n);
            m_samples.insert(std::make_pair(n,tem));
            /* pour enfin récupérer l'itérateur sur l'objet fraichement ajouté */
            it=m_samples.find(n);
        }
        /* une fois que l'on est ici, nous savons avoir l'itérateur demandé */
        (*it).second->lire;
    }
    Ton gestionnaire n'aurait alors pas de fonction sample_fini mais aurait par contre une fonction delete_sample codée sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void gestionnaire::delete_sample(std::string const & n)
    {
        //on cherche l'échantillon à supprimer
        std::map<std::string,sample*>::iterator it= m_samples.find(n);
        // s'il est trouvé, on l'efface correctement, sinon, on ne fait rien
        if(it!=m_samples.end())
        {
            delete (*it).second;
            m_samples.erase(it);
        }
    }
    qui ne serait appelée que lorsque l'on sait pertinemment que le son ne devra plus être rejoué dans un avenir proche

    EDIT: de manière générale, cette fonction n'étant destinée à être appelée qu'à la fin d'un niveau ou d'un plateau du jeu, elle peut ne pas être thread safe, étant donné que l'on peut considérer qu'il n'y aura qu'un seul thread qui se chargera du "nettoyage"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre régulier
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    162
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Mai 2002
    Messages : 162
    Points : 88
    Points
    88
    Par défaut
    Citation Envoyé par koala01 Voir le message
    • Ce n'est pas forcément parce qu'un échantillon de son a fini d'être joué qu'il ne devra pas... être rejoué plus tard
    • je présume que tes échantillons de sons sont chargés depuis des fichiers présents sur le disque dur
    • La création et le chargement de ces sons (si mon hypothèse ci-dessus est correcte) demande énormément de temps
    Citation Envoyé par koala01 Voir le message
    Je me demande donc s'il ne serait pas intéressant de "revoir ta copie" au niveau du gestionnaire de son, de manière à utiliser, par exemple, un conteneur associatif (une std::map ) et à garder en mémoire les sons jusqu'à ce que tu saches qu'ils ne sont plus nécessaire (AKA: jusqu'à la fin du niveau / du plateau ou jusqu'à la fin du jeu).
    À vrai dire, j'ai deux objets pour gérer les sons. L'objet «*son*» est ce que tu listes. Il correspond en fait aux données du son, chargées depuis un fichier. Il est créé au début du niveau et détruit à la fin.

    Ensuite, j'ai un objet «*sample*», qui est une sorte d'exemplaire en lecture d'un son. Il ne contient pas les données à jouer, mais garde le channel créé par la sdl, ainsi que divers paramètres comme le volume ou la position de l'émetteur. Ainsi, je peux avoir plusieurs samples pour un même son.

    Le gestionnaire de son garde un pointeur sur chaque sample pour plusieurs raisons. D'une part pour permettre de jouer un sample sans avoir à le surveiller. Le gestionnaire se chargera de le détruire quand il sera terminé, ou quand le niveau sera terminé. Il y a aussi le cas particulier de musiques. Il y a un système pour que lorsqu'une nouvelle musique est jouée, l'ancienne soit mise en attente en attendant que la nouvelle se termine.

    Le problème intervient donc quand un sample se termine. Le gestionnaire est prévenu pour faire libérer la mémoire du sample et restaurer les samples en attente. J'imagine que si plusieurs samples se terminent simultanément, il va y avoir un problème dans la boucle de suppression et je me demande si le système des mutex suffira à éviter les problèmes.

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Au temps pour moi, je n'avais pas compris les choses sous cet angle

    Pour répondre à ta question, oui, un mutex devrait pouvoir suffire, mais, il faut savoir que si tu le place mal, tu risque de "bloquer" le thread qui appelle la suppression de l'échantillon le temps que la première suppression demandée soit réellement effectuée.

    Si tu dois trouver un échantillon parmi de nombreux autres, cela peut devenir embêtant

    L'idée pourrait donc être de placer l'échantillon à supprimer dans une pile ou dans une file quoi qu'il arrive, puis de se baser sur le mutex pour savoir si une suppression est en cours.

    Si aucune suppression n'est en cours, on supprime l'échantillon accessible depuis la pile ou la file (ce qui correspond au premier de la file ou au dernier échantillon de la pile), puis on boucle sur la pile (ou sur la file) pour supprimer les précédents (suivants), et on supprime le mutex une fois la pile (ou la file) vide

    si une suppression est en cours (ce qui est donc vérifié APRES insertion dans la pile /file), on quitte la fonction, ce qui libère le thread qui a demandé la suppression
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. generateur de code java pour rechercher dans une base de donnees
    Par cdubet dans le forum EDI et Outils pour Java
    Réponses: 5
    Dernier message: 18/02/2017, 10h10
  2. VC++ CLR, delegate anonyme pour FindIndex dans une List
    Par Linkman_xbp dans le forum Général Dotnet
    Réponses: 3
    Dernier message: 08/11/2009, 16h55
  3. [MySQL] Image pour supprimer dans une base de données
    Par fabpeden dans le forum PHP & Base de données
    Réponses: 4
    Dernier message: 18/07/2007, 15h21
  4. Réponses: 3
    Dernier message: 21/10/2006, 12h39
  5. Réponses: 2
    Dernier message: 07/07/2006, 10h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo