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

Boost C++ Discussion :

[boost::thread] [mutex] Threader une méthode


Sujet :

Boost C++

  1. #1
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut [boost::thread] [mutex] Threader une méthode
    Bonjour.

    J'ai besoin d'utiliser les thread pour un projet, j'utilise donc boost::thread.
    Mon but est de threader une fonction membre d'une classe qui est non copiable (boost::noncopyable), je ne sait pas si la technique que j'ai mis en place est la bonne, mais en utilisant un foncteur copiable a l'intérieur de ma classe, j'ai pu réaliser mon thread.
    Mais le problème est que je vais devoir bloquer des threads lors de l'utilisation de données communes (avec un mutex ?), mais je n'ai pas très bien compris comment fonctionnait ces mutex (j'ai lu la doc et le tuto du site, mais je suis pas sur d'avoir compris) :
    -> Je doit créer autant de mutex que de variables à protéger ?
    -> Il faut créer des mutex pour toutes les variables partagées, ou seulement pour celles dont l'état est modifié par la fonction ?
    -> Si j'ai bien compris je doit créer le mutex (boost::mutex mutex le vérouiller dans un bloc ({boost::mutex::scoped_lock lock(mutex); /*traitement de la variable*/}) et il sera automatiquement dévérouiller a la sortie du bloc (RAII) ?
    -> Dans mon code (ci dessous), je doit protéger tous les attributs utilisé à la fois par les fonctions membres et par le thread (directement ou indirectement) ?
    -> Je ne vois pas trop comment gérer m_continuer, comme les mutex sont dévérouillés automatiquement a la sortie d'un bloc (RAII) comment faire pour garantir que stop() n'accédera pas a m_continuer en même temps que le while ?
    -> (question qui n'a rien a voir avec les threads) c'est normal que j'ai un warning quand je rajoute boost::noncopyable à B (warning: direct base 'boost::noncopyable_::noncopyable' inaccessible in 'NAMESPACE_PERSO::A<T>::B' due to ambiguity)
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
     
    #include <iostream>
    #include <boost/thread.hpp>
    #include <cstdlib>
    #include <boost/utility.hpp>
     
    template <class T>
    class A : boost::noncopyable
    {
    	private:
    		class B : public T
    		{
    			public:
    				void update(/*différent paramètres*/)
    				{
    					//effectue un traitement sur les attribut de B en fonction des paramètres
    					T::update(/*différent paramètres dépendant des attributs du B*/);
    				}
     
    			private:
    				//différent attributs
    		};
    		class ThreadFunc //class servant d'intermédiaire pour threader A() avec A non copiable
    		{
    			public :
    				ThreadFunc(A* a) : m_a(a) {}
    				void operator()()
    				{
    					(*m_a)();
    				}
     
    			private :
    				A* m_a;
    		};
     
    	public:
    		A() : m_thread(NULL) {}
    		~A()
    		{
    			//+ autre opération de libération de certains attribut alloué dynamiquement
    			delete m_thread;
    		}
     
    		void operator()()
    		{
    			while(m_continuer)
    			{
    				//calcul du paramètre a passer a update, dépend d'un attribut de A
    				update(/*un paramètre*/);
    			}
    		}
     
    		T* add_b()
    		{
    			//ajoute un elt au conteneur de B, et retourne un T* sur cet elt
    		}
    		//+ une autre méthode ajoute un elt a l'autre conteneur
    		void run () //lancer le thread
    		{
    			m_continuer = true;
    			m_thread = new boost::thread(ThreadFunc(this));
    		}
    		void stop () //arretter le thread
    		{
    			m_continuer = false;
    			m_thread->join();
    		}
     
    	private:
    		void update (/*un paramètre*/)
    		{
    			//effectue des calcul dépendant des conteneurs, et appèle update pour tout les B du conteneur
    		}
     
     
    		bool m_continuer;
    		boost::thread* m_thread;
    		//+ un conteneur de B
    		//+ un autre conteneur
    };
     
    int main( ) 
    {
    	A<T> a;
     
    	T* t = a.add_b();
     
    	a.run();
     
    	//utilisation des diiférente méthode de T sur t
     
    	a.stop();
     
    	 return EXIT_SUCCESS;
    }
    Merci d'avance.

  2. #2
    Membre expérimenté Avatar de vikki
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    292
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Mai 2007
    Messages : 292
    Par défaut
    Hello,
    Je ne suis pas un pro des thread et mutex, mais je peux essayer de répondre à quelques questions.
    Tu n'est pas obliger de créer un mutex par variable à protéger. Si ces variables sont appelées dans un même bloc, un mutex suffit pour toutes.
    Pour le verrouillage du mutex, c'est bien ca. Il existe plusieurs stratégies de lock, un article disponible sur le site pourra te résumer tous ca.
    Si je ne dis pas de bêtises, stop() peut accéder à m_continuer en même temps que le while, les problemes surviennent lorsqu'une ressource est utiliser en écriture simultanément dans 2 blocs (le while ne fait que lire).
    Sinon, les thread de boost s'utilisent généralement conjointement a boost::bind qui te permet d'encapsuler un pointeur sur ta fonction et les arguments associés. Cela donnerait:
    m_thread = new boost::thread(boost::bind(&A<T>::operator(),this)); //un truc comme ca

  3. #3
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Pourquoi t'utilises new ?

  4. #4
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Bonjour,
    Il me semble que tu t'es emmêlé les pinceaux avec cette histoire de foncteur ThreadFunc et d'operator() car la fonction run() de A... ne fait rien. (plus précisément elle crée un thread qui ne fait rien.)

    Et justement l'intérêt de boost::thread c'est qu'on a pas besoin de faire ce genre d'acrobatie, car il est possible de passer n'importe quoi à un thread tant que ça ressemble à une fonction : fonction libre, fonction membre, foncteur...
    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
    26
    27
    28
    29
    30
    31
    32
     
    #include "boost/thread.hpp"
     
    class Parameter
    {
    };
     
    class Data
    {
    public:
       void DoStuff(const Parameter& param, int otherParam){}	
    };
     
    class A
    {
    public:
       void SomeFunc(const Parameter& param, int otherParam) 
       {
          data_.DoStuff(param, otherParam);
       }
    private:
       Data data_;
    };
     
    int main()
    {
       A a;
       Parameter param;
       boost::thread my_thread(&A::SomeFunc, &a, boost::cref(param), 5);
       my_thread.join(); 
       return 0;
    }
    Autre chose en passant : je subodore que la classe A est déjà bien assez complexe comme ça et que la gestion du multi-thread doit commencer à embrouiller la lecture. Il me semble donc que l'on peut déporter toutes ces opérations dans une autre classe qui ne s'occupera que de ça pour alléger un peu :

    Quelque chose du genre :
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
     
    template<typename T>
    class ScopedAsynchUpdateA : boost::noncopyable
    {
    public:
       ScopedAsynchUpdateA(A<T>& a):
       a_(a),
       stopThread_(false),
       thread_(&ScopedAsynchUpdateA<T>::Update, this)
       {
       }
     
       ~ScopedAsynchUpdateA()
       {
          Stop(); // au cas où l'utilisateur a oublié...
       }
     
       void Stop(void)
       {
          stopThread_ = true;
          thread_.join();
       }
     
    private:
       void Update()
       {
          while(!stopThread_)
          {
             a_.update();
             boost::this_thread::sleep(boost::posix_time::millisec(100)); // sinon le while va bouffer tout le processeur
          }
       } 
     
       A<T>& a_;
     
       // attention à l'ordre, le bool doit être déclaré AVANT le thread
       // pour être initialisé en premier dans le constructeur
       bool stopThread_; 
       boost::thread thread_;
    };
    (avec la fonction update de A en privé, pour éviter quelle soit appelée de n'importe où, et avec ScopedAsynchUpdateA friend de A. Comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    template <typename T>
    class ScopedAsynchUpdateA;
     
    template <class T>
    class A : boost::noncopyable
    {
    private:
       friend ScopedAsynchUpdateA<T>;
       void update();
    ...

    > Je doit créer autant de mutex que de variables à protéger ?
    -> Il faut créer des mutex pour toutes les variables partagées, ou seulement pour celles dont l'état est modifié par la fonction ?
    etc...
    Il n'y a pas de recette miracle.
    Il faut étudier attentivement l'interface de la classe et vérifier soigneusement qui à accès à quoi pour bien placer ses lock. Malheureusement, une erreur fini souvent en deadlock ou en race condition qui sont un enfer à debugger...

    Par exemple, dans le code que tu as donné je tique sur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    T* t = a.add_b();
    a.run();
    //utilisation des diiférente méthode de T sur t
    a.stop();
    Ce genre de code peut rapidement conduire à une catastrophe en multi-threading car dès qu'on autorise un accès interne à la classe par pointeur ou par référence alors se pose le problème des accès concurrents. Par exemple ici, le thread d'update appelle T::update sur l'ensemble des T pendant que le thread principal "utilise différente méthode de T sur t" -> BOUM. On pourrait penser qu'un mutex dans la classe B corrige le problème...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    class B : public T
    {
    public:
       void update(/*différent paramètres*/)
       {
          boost::lock_guard(mutex_);
          //effectue un traitement sur les attribut de B en fonction des paramètres
          T::update(/*différent paramètres dépendant des attributs du B*/);
       }
    private:
       boost::mutex mutex_;
    };
    ... mais ça ne marche pas, car justement le thread principal contourne le mutex en passant directement par le pointeur sur T...
    Il y a un problème de conception. Il va falloir que tu trouves un moyen pour contrôler les accès à T.

  5. #5
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut Réponse
    Merci pour ces précision, je vais pouvoir simplifier.

    Sinon, je me doutais bien qu'il y allait avoir un problème d'accés concourant pour les pointeur sur T. Mais je ne vois pas trop comment régler ce problème. En faite la classe A a pour but de gérer des points (le B) dont l'utilsateur n'a pas a avoir connaissance. Le paramètre template T permet a l'utisateur de définir une interface pour les points, update permet de synchroniser l'interface utilisateur d'un point avec le type B (T peut par exemple permettre un affichage) et donc je retournais un pointeur sur T pour permetre a l'utilisateur d'utiliser ses points directement, sans avoir a passer par A qui a juste pour but de définir un contexte d'évolution pour ses points mais pas de permettre un accés. Est ce que encapsuler chaque pointeur sur T dans une classe me permettrait de protéger l'accés au données ?

    Pour le new c'était juste parce que au début je ne pouvais pas créer le thread au début, et qu'il vallait mieux l'initialiser après, mais finalement je vais pouvoir ôter le pointeur sur boost::thread.

  6. #6
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    J'ai corrigé ma classe, et ca marche comme je le voulait, il ne reste que le problème des acces concourrant dans les T.

    J'ai trouvé une solution, mais elle me satisfait très car elle laisse une grande responsabilité à l'utilisateur.
    J'ai mis un mutex en attribut public dans ma classe A, je bloque ce mutex quand je fais mes appels à A.update, et je laisse la responsabilité a l'utilisateur de bloquer le mutex (comme il est en public) dès qu'il utilise une méthode de T sucseptible de modifié les même attributs que update (de T). Est ce une bonne méthode, ou à éviter ?

    Sinon j'ai mis un 2° mutex que je bloque quand je parcour mon conteneur de A et quand j'ajoute un élément dans ce conteneur.

Discussions similaires

  1. Réponses: 10
    Dernier message: 31/03/2010, 23h34
  2. Réponses: 3
    Dernier message: 19/03/2008, 09h38
  3. Réponses: 9
    Dernier message: 24/08/2007, 12h37
  4. Threads et arrêt d'une méthode
    Par oc_alex86 dans le forum Concurrence et multi-thread
    Réponses: 6
    Dernier message: 25/05/2007, 19h12
  5. Exécution d'un Thread dans une méthode statique
    Par barbiche dans le forum Débuter avec Java
    Réponses: 3
    Dernier message: 03/05/2007, 14h25

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