3 votes

Classe de threads portable implémentée en C++

Lors de l'écriture de programmes C qui doivent partager une variable de portée de fichier entre l'application et une routine d'interruption/un thread/une routine de callback, il est bien connu que la variable doit être déclarée volatile, sinon le compilateur peut effectuer des optimisations incorrectes. Voici un exemple de ce que je veux dire :

int flag;

void some_interrupt (void)
{
  flag = 1;
}

int main()
{
  flag = 0;
  ...

  /* <-- interrupt occurs here */

  x = flag; /* BUG: the compiler doesn't realize that "flag" was changed 
                    and sets x to 0 even though flag==1 */

}

Pour éviter le bogue ci-dessus, "flag" aurait dû être déclaré comme volatile.

Ma question est la suivante : comment cela s'applique-t-il au C++ lors de la création d'une classe contenant un thread ?

J'ai une classe qui ressemble à quelque chose comme ça :

class My_thread
{
  private:
    int flag;

    static void thread_func (void* some_arg) // thread callback function
    {
      My_thread* this_ptr= (My_thread*)some_arg;

    }
};

"some_arg" contiendra un pointeur vers une instance de la classe, de sorte que chaque objet de "My_thread" possède son propre thread. Grâce à ce pointeur, il accédera aux variables membres.

Cela signifie-t-il que "this_ptr" doit être déclaré comme un pointeur vers des données volatiles ? Le "flag" doit-il également être volatil ? Et si oui, dois-je rendre volatiles toutes les fonctions membres qui modifient "flag" ?

Je ne suis pas intéressé par le comportement d'un système d'exploitation ou d'un compilateur particulier, je cherche une solution générique et totalement portable.

EDIT : Cette question n'a rien à voir avec la sécurité des fils.

Le vrai code aura des sémaphores, etc.

Pour clarifier, je souhaite éviter les bogues causés par l'ignorance du compilateur du fait qu'une fonction de rappel peut être appelée depuis des sources extérieures au programme lui-même, et donc tirer des conclusions incorrectes sur l'utilisation ou non de certaines variables. Je sais comment faire cela en C, comme l'illustre le premier exemple, mais pas en C++.

2voto

MSalters Points 74024

Eh bien, ce montage fait toute la différence du monde. Les sémaphores introduisent des barrières de mémoire. Celles-ci font volatile redondant. Le compilateur rechargera toujours int flag après toute opération sur un sémaphore.

Fred Larson l'avait déjà prédit. volatile est insuffisante en l'absence de verrous, et réductible en présence de verrous. Cela le rend inutile pour la programmation thread-safe.

0voto

vidstige Points 4541

D'après la signature du pointeur de fonction, je suppose que vous utilisez l'implémentation posix thread pour les threads. Je suppose que vous voulez savoir comment démarrer un thread en utilisant cette API. Tout d'abord, envisagez d'utiliser fil conducteur à la place. Si ce n'est pas possible, j'opte généralement pour quelque chose comme ce qui suit pour obtenir un peu de cette lisibilité Java confortable.

class Runnable {
    public:
        virtual void run() = 0;
};

class Thread : public Runnable {
    public:
        Thread();
        Thread(Runnable *r);

        void start();
        void join();        

        pthread_t getPthread() const;

    private:

        static void *start_routine(void *object);

        Runnable *runner;
        pthread_t thread;
};

Et puis quelque chose comme ça dans le start_routine fonction :

void* Thread::start_routine(void *object) {
    Runnable *o = (Runnable *)object;

    o->run();

    pthread_exit(NULL);
    return NULL;
}

Désormais, l'accès aux champs des classes qui étendent la classe Runnable ou Thread n'a pas besoin d'être volatile puisqu'elles sont thread-local .

Cela dit, partage Les données entre les threads sont plus complexes que l'utilisation d'un membre de données volatiles, malheureusement si c'est ce que vous avez demandé...

0voto

Fredrik R Points 31

Une implémentation du mécanisme de repli est donnée ici pour Windows et Linux. Essayez cet exemple :

typeReadFileCallback varCallback;

J'ai pu mettre en œuvre en utilisant cela.

0voto

Robert S. Barnes Points 17244

Lisez cet article d'Andrei Alexandrescu sur Dr. Dobbs, il pourrait être pertinent :

volatile - Le meilleur ami du programmeur multithreadé

Extrait de l'introduction de l'article :

Le mot-clé volatile a été conçu pour empêcher les optimisations du compilateur qui qui pourraient rendre le code incorrect en présence de certains événements asynchrones. Par exemple, si vous déclarez une variable variable primitive comme volatile, le compilateur n'est pas autorisé à la mettre en cache dans un registre -- une optimisation commune qui serait désastreuse si cette variable était partagée entre plusieurs threads. Donc la règle générale est que, si vous avez des variables de type primitif qui doivent être partagées entre plusieurs threads, déclarez ces variables volatiles. Mais vous pouvez en fait faire beaucoup plus avec ce mot-clé : vous pouvez l'utiliser pour attraper le code qui n'est pas thread safe, et ce, dès la compilation compilation. Cet article montre comment comment le faire ; la solution implique un simple pointeur intelligent qui permet également facile de sérialiser les sections critiques critiques du code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X