Forum Programmation.c probleme avec le fonctionnement d'un thread

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
1
13
oct.
2019

bonjour,

voila je vous poste mon code ca sera plus simple pour la suite d'expliquer mon probleme.

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>

    void* f(void * arg)
    {
        int value = *((int*)arg);
        fprintf(stdout, "thread : &value  = %p ; value = %i\n", arg, value);

    }

    int main(int argc, char const *argv[])
    {
        pthread_t t;
        int value = 6;

        pthread_create( &t, NULL, f, (void*)&value);

        printf("&value = %p ; value = %i\n", &value , value);

        pthread_join( t, NULL );
        return 0;
    }

D'apres ce que j'ai compris des threads, c'est qu'ils font partie du meme processus mais ils possedent chacun leurs propres piles (stack). Donc les Threads peuvent lire des variables dans le tas ou dans la zones ou sont stockés les variables globales, mais il n'est pas possible de lire une variable dans la pile d'un autre thread sans déclencher un segfault.

Or dans mon code je vais lire a partir d'un thread secondaire, une variable qui se trouve sur la pile du thread principale et ca ne déclenche aucun segfault. J'en déduis donc qu'il y a quelque chose que je n'ai pas compris.

Pouvez vous m'éclairer sur ce probleme ?

Merci d'avance

  • # pas de protection mémoire

    Posté par  . Évalué à 3.

    Bonjour,

    Tout d'abord, il n'y a aucun cloisonement de la mémoire entre les différents threads d'un processus. Le thread secondaire peut donc tout à fait accéder à une variable stockée dans la pile du premier, à condition bien sûr de connaître son adresse.

    Par contre, les piles sont bien indépendantes ce qui est nécessaire pour que les différents threads puissent (facilement) appeler des fonctions, créer des variables locales…

    Après on évite généralement systématiquement de partager des variables stockées dans la pile d'un autre thread car il devient rapidement compliqué de garantir que celles-ci existeront toujours au moment de l'accès.
    Un moyen plus sûr serait que le thread "principal" créé la variable dans le tas (malloc()), l'initialise, fasse ce qu'il a faire avec et seulement ensuite transmette l'adresse de celle-ci au thread secondaire qui se chargera de libérer la variable (free()).

    Les vrais naviguent en -42

    • [^] # Re: pas de protection mémoire

      Posté par  . Évalué à 3.

      Un moyen plus sûr serait que le thread "principal" créé la variable dans le tas (malloc()), l'initialise, fasse ce qu'il a faire avec et seulement ensuite transmette l'adresse de celle-ci au thread secondaire qui se chargera de libérer la variable (free()).

      Bof … Moi je demanderais au thread secondaire de prévenir le thread principal qu'il n'a plus besoin de la ressource pour qu'il la libère. En effet, qu'est-ce qui dit qu'un autre thread secondaire n'aurait pas besoin de cette ressource ? Apres c'est une question de choix.

      • [^] # Re: pas de protection mémoire

        Posté par  . Évalué à 1.

        Ouh là… on va partir dans une discussion qui dépasse de loin la question initiale, mais pourquoi pas.

        Le moyen que j'ai indiqué ne correspond bien évidement pas à tous les cas qui peuvent arriver, d'ailleurs la libération d'une variable dépend toujours du contenu et du rôle de celle-ci.

        Souvent dans mes logiciels, la variable passée lors de la création du thread correspond à un "ordre de mission" pour le thread (quelle donnée traitée, quel traitement, où stocker le résultat…). Dans ce cas je voir ça comme un "don" et le destinataire est chargé de détruire proprement la variable lorsqu'il n'en a plus besoin.
        Cette approche a l'avantage de supprimer les risques d'accès concurrents (par exemple la modifications d'une structure alors qu'elle est en cours de lecture par un autre thread).

        Un autre cas est la variable partagée, mais dans ce cas il va souvent falloir ajouter les mutex pour gérer les accès concurrents ce qui n'est pas ce qui va assurer les meilleurs performances. Dans ce cas, le thread créant la variable pourrait être vu comme le propriétaire de la variable et aura donc à gérer sa destruction au moment opportun (soit la méthode que tu as proposée).

        Les vrais naviguent en -42

        • [^] # Re: pas de protection mémoire

          Posté par  (site web personnel) . Évalué à 4.

          Un autre cas est la variable partagée, mais dans ce cas il va souvent falloir ajouter les mutex pour gérer les accès concurrents ce qui n'est pas ce qui va assurer les meilleurs performances.

          C'est pourquoi je cherche autant que possible à utiliser des pipes entre les threads. C'est une solution plus légère pour gérer la concurrence.

        • [^] # Re: pas de protection mémoire

          Posté par  . Évalué à 3.

          Je préfère passer par la pile pour passer les paramètres à un thread en utilisant les conditions.

          Du coup sur le thread principal c’est préparation de la condition, démarrage du thread attente que le thread valide la condition.
          Côté thread, lecture des paramètres qui se trouve dans la pile du thread principal, validation de la condition pour permettre au créateur de continuer son travail.

  • # Pour ce que j'en sais...

    Posté par  . Évalué à -2.

    Au risque de dire une sottise, je crois que cela fonctionne comme pour le fork, les variables déclarées avant le fork()/pthread_create() sont connues de tous.

    ++
    Gi)

    • [^] # Re: Pour ce que j'en sais...

      Posté par  (site web personnel) . Évalué à 5.

      Avec une très grosse différence : avec fork, la mémoire est en copy-on-write, donc les accès en écriture d'un processus ne sont pas visibles depuis un autre processus. Avec pthread_create, la mémoire est vraiment partagée, en lecture et en écriture.

  • # espace utilisateur

    Posté par  (site web personnel) . Évalué à 4. Dernière modification le 13 octobre 2019 à 15:39.

    D'apres ce que j'ai compris des threads, c'est qu'ils font partie du meme processus

    Oui, ils vivent dans le même espace mémoire virtuel.

    mais ils possedent chacun leurs propres piles (stack).

    Oui, allouées dans cet espace.

    Donc les Threads peuvent lire des variables dans le tas ou dans la zones ou sont stockés les variables globales,

    De fait, ils peuvent lire des variables allouées dans l'espace mémoire dédié au processus.

    mais il n'est pas possible de lire une variable dans la pile d'un autre thread sans déclencher un segfault.

    Ah ? D'où tenez vous cette assertion ?

    Votre programme, qui est tout à fait valide, est la preuve du contraire.

    Le risque avec les threads est de mal maîtriser les flux d’exécution et de demander à accéder à une variable qui a été libérée, que soit parce qu'elle était dans le scope d'un thread qui s'est terminé ou parce qu'elle a été libérée explicitement.

    Et même là, le risque de segfault est faible, vous allez probablement taper dans un espace toujours réservé pour votre processus. Par contre, pour ce qui est de la valeur que vous allez récupérer, ça dépendra du sens du vent.

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* g(void * arg)
    {
        int value = *((int*)arg);
        sleep(2);
        fprintf(stdout, "thread g : &value  = %p ; value = %i\n", arg, value);
        return NULL;
    
    }
    void* f(void * arg)
    {
        pthread_t t;
        int value = *((int*)arg);
        pthread_create( &t, NULL, g, (void*)&value);
        sleep(1);
        fprintf(stdout, "thread f: &value  = %p ; value = %i\n", arg, value);
    
        printf("bye thread f\n");
        return NULL;
    }
    
    int main(int argc, char const *argv[])
    {
        pthread_t t;
        int *value = malloc(sizeof (int));
        *value=6;
    
    
        printf("&value = %p ; value = %i\n", value , *value);
        pthread_create( &t, NULL, f, value);
        free(value);
        value=NULL;
        printf("value freed\n");
    
    
        pthread_join( t, NULL );
        sleep(5);
        return 0;
    }
  • # c'est compris

    Posté par  . Évalué à 1.

    c'est beaucoup plus claire merci d'avance pour vos éclaircissements

  • # Clarification de undefined behavior

    Posté par  (site web personnel) . Évalué à 1.

    Plop,

    Beaucoup de gens pensent à tort que “Undefined Behavior” va générer un crash. Ce n'est pas requis. Par exemple, rien n'empêche à une implémentation d'autoriser l'écriture sur un pointeur nul, la norme dit simplement que c'est un comportement indéfini.

    Ici il n'y a aucune raison que ça crash d'autant que le code est valide puisque tu ne modifie pas la variable dans ton thread que tu as passé en argument. Et encore heureux qu'on puisse lire les objets passés en arguments dans un thread sinon bonjour leur utilité ! ;-)

    git is great because linus did it, mercurial is better because he didn't

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.