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

 C Discussion :

Threads / Langage C


Sujet :

C

  1. #1
    Nouveau membre du Club
    Threads / Langage C
    Hello tous,

    Je débute en C, j'ai un p'tit devoir maison à faire et je bug sur une question...
    Si vous pouviez me donner votre avis ça serait top !

    On me donne ce 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
     
    #include <pthread.h> 
    #include <stdio.h> 
    int value = 0; 
     
    /* The thread will begin control in this function */ 
    void *runner(void *param) { 
      value = 5; 
      pthread_exit(0); 
    } 
     
    int main(int argc, char *argv[]) { 
      int pid; 
      pthread_t tid; /* the thread identifier */ 
      pid = fork(); 
     
      if (!pid) { /* Child process */ 
        pthread_create(&tid, &attr, runner, NULL); 
        pthread_join(tid, NULL); 
        printf("CHILD : value = %d\n", value); /* LINE C */ 
      } else { /* Parent process */ 
        wait(NULL); 
        printf("PARENT: value = %d\n", value); /* LINE P */ 
      } 
    }


    Et les questions sont les suivantes...
    Bon, je vous la fait courte, voici ce que j'ai répondu :

    1. Décrire l’action de pid = fork();
      On fork le processus appelant, il va ainsi se dupliquer. Dans le processus père pid sera égal à 0 (retourné par le fork) tandis que dans le processus fils on aura pid = pid du fils.
    2. Décrire l’action de pthread_create (...);
      Là ben je renvoie vers le manuel et j'explique un peu (qu'on crée le thread tid numéro untel)
    3. Qu’est ce qui est affichées dans les lignes LINE C et LINE P? Expliquez
      Bon ben là je bug, pour moi le code proposé est faux et ils ont inversé le père et le fils...
      Merci de me corriger ou me confirmer.

      Dans le process père (pid 0) on lance la fonction runner en créant le thread, tandis que dans le fils ben on ne fait qu'afficher la value.
    4. Quel résultat génère-t-on si on remplace wait() par pthread_join()?
      Ben la pour moi ça boucle, il faut stopper le process avec ctrl + C
      Mon intuition me dit que le thread du père n’est pas joignable depuis le fils s’agissant de deux processus distincts. (le vrais fils donc, pas celui comme indiqué dans le code)


    Voilà merci pour tout et la bise à mes collègues s'ils atterrissent sur ce post en réalisant leur tp

  2. #2
    Expert confirmé
    Bonjour,

    3. Non le code est correct. Le père voit un pid non nul et donc déroule le else. Le fils a créé un thread et a attendu sa fin, donc la variable value a pris la valeur 5. La variable value du père quant à elle est restée à 0.

    4. Ça n'a pas de sens pour le père d'attendre la fin d'un thread alors qu'il n'en a pas créé, ici la variable tid a même une valeur imprévisible. Donc appeler pthred_join est un undefined behavior, tout et rien peut arriver!

  3. #3
    Nouveau membre du Club
    Yessssss en effet, en relisant le truc bien au calme à l'ancienne avec un papier et un crayon, le père et le fils sont bien au bon endroit.
    J'ai bien compris le comment pourquoi la variable passe à 5 d'un côté et pas de l'autre, mais le coup du pthread je n'y comprenait rien ! (puisqu'il n'a pas de sens).
    Donc ma question c'était surtout de savoir si le père et le fils étaient liés par le thread mais non, on est bien d'accord que hormis le ppid, ce sont deux process distincts !?
    Citation Envoyé par dalfab Voir le message
    Donc appeler pthred_join est un undefined behavior, tout et rien peut arriver!
    Merci vraiment pour cette info dalfab, j'aime la précision et je vais creuser un peu du côté des erreurs.
    Bon bah je passe ça en résolu

  4. #4
    Expert confirmé
    Oui, il ne faut pas confondre processus et threads.

    Après un fork() on a 2 processus pour lesquels initialement le fils est la copie à l'identique de son père (donc à cet instant toutes les variables ont été clonées et ont la même valeur. Si des fichiers ont été ouverts avant, les deux processus partent du même état fichier, ...) Après le fork() chacun vis sa vie sans aucun point commun donc aucun risque de collisions.

    Les threads par contre sont un traitement en parallèle comme les processus mais ils sont dans le même processus donc dans le même espace mémoire. Par exemple, une variable globale sera accessible simultanément par tous les threads du processus. C'est beaucoup plus puissant mais le risque de collision est complexe à gérer.

  5. #5
    Nouveau membre du Club
    Si j'ai bien suivi... tid serait accessible sans problème par le pthread_join depuis le père comme depuis le fils si le pthread_create avait été réalisé avant le fork c'est cela ? mais ce serait la meme variable (même adresse mémoire). risqué d'y toucher par la suite non ou les variables sont elles aussi dupliquée lors du fork ?

    Si le pthread_create avait été réalisé après le fork mais avant le if) pas de souci car chaque processus aurait fait le sien (on aurait donc 4 thread) mais donc dans ce cas j'en reviens à la variable value.... qui est modifiée par runner depuis le fils. C'est la même variable pour tous ???

    En tout cas milles mercis dalfab de me transmettre humainement tous les secrets des process et des threads

  6. #6
    Expert confirmé
    Citation Envoyé par viry0ne Voir le message
    Si j'ai bien suivi... tid serait accessible sans problème par le pthread_join depuis le père comme depuis le fils si le pthread_create avait été réalisé avant le fork c'est cela ?
    Tout à fait. Mais attention si des threads existent avant le fork(), Le comportement est très particulier d'ailleurs je crois que POSIX ne l'a pas fixé (chaque système d'exploitation a son comportement)
    Citation Envoyé par viry0ne Voir le message
    mais ce serait la meme variable (même adresse mémoire). risqué d'y toucher par la suite non ou les variables sont elles aussi dupliquée lors du fork ?
    Tout est dupliqué. Les 2 processus sont différents, chacun a sa variable. Rien n'est commun à partir du fork(). Paradoxalement si tu affiches l'adresse mémoire, ils peuvent indiquer le même nombre. Mais ça n'est pas la même mémoire, si si!!! Un système appelé MMU cloisonne les processus.
    Citation Envoyé par viry0ne Voir le message
    Si le pthread_create avait été réalisé après le fork mais avant le if) pas de souci car chaque processus aurait fait le sien (on aurait donc 4 thread) mais donc dans ce cas j'en reviens à la variable value.... qui est modifiée par runner depuis le fils. C'est la même variable pour tous ???
    Non, il y 2 processus donc 2 variables.

  7. #7
    Expert éminent sénior
    Bonjour
    Citation Envoyé par viry0ne Voir le message
    Dans le processus père pid sera égal à 0 (retourné par le fork) tandis que dans le processus fils on aura pid = pid du fils.
    Non, c'est idiot que le père reçoive 0 tandis que le fils (qui, comme Socrate, se connait lui-même et donc connait déjà son pid avec getpid()) reçoive alors son propre pid !!!
    C'est l'inverse: le père reçoit du fork() le pid du fils créé (comme ça il le connait) et le fils, qui n'a pas besoin de connaitre quoi que ce soit (il connait aussi son père avec getppid()) reçoit 0.

    Accessoirement la variable "attr" n'est pas définie.
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  8. #8
    Nouveau membre du Club
    J'ADORE LA REFERENCE !!! Je n'oublierais plus jamais
    Citation Envoyé par Sve@r Voir le message
    qui, comme Socrate, se connait lui-même
    Par contre keskecé attr ???
    Citation Envoyé par Sve@r Voir le message
    Accessoirement la variable "attr" n'est pas définie.
    En tout cas merci, faudra que j'aille jeter un œil à ton tuto bash, ça aussi c'est un truc que je dois apprendre a faire

  9. #9
    Expert éminent sénior
    Citation Envoyé par viry0ne Voir le message
    Par contre keskecé attr ???
    Ligne 18 de ton premier code: pthread_create(&tid, &attr, runner, NULL);. La variable "attr" n'a pas été définie.

    Citation Envoyé par viry0ne Voir le message
    En tout cas merci, faudra que j'aille jeter un œil à ton tuto bash, ça aussi c'est un truc que je dois apprendre a faire
    Vas-y. Le shell est un chouette langage pour automatiser les manipulations répétitives dans la gestion des fichiers sous Unix/Linux...
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  10. #10
    Nouveau membre du Club
    Citation Envoyé par Sve@r Voir le message
    Ligne 18 de ton premier code
    Exacttttt c'est parce que j'ai repris le code de l'énoncé (truffé de coquilles), dans la rédaction de mon rendu, le prof demande d'expliquer le pthread_create et j'ai dit ça pour cet argument : pthread_attr_t = permet de définir des attributs spécifiques pour chaque thread, dans notre cas on se contente des attributs par défaut en mettant cet argument à NULL.
    Quel oeil de lynx , encore merci pour tout ! @bientôt

  11. #11
    Rédacteur/Modérateur

    Si l'argument doit être à null la variable est inutile.
    Là ton argument est tout sauf nul, il est initialisé avec des valeurs aléatoires. Enfin s'il existait et que le programme compilait.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  12. #12
    Nouveau membre du Club
    heu non le null est pas inutile parce que ça fait partie de la forme de la fonction ... (enfin de ce que j'ai compris).
    man pthread_create

    Moi dans le code que j'ai exécuté, je l'ai mis à null (je ne me suis pas servie de la variable).
    Du coup je ne sais pas ce que ça fait si je n'avait pas mis null mais je suppose que ça ne compilerait pas (la variable n'étant pas déclarée, et si elle l'était aucune idée !)

  13. #13
    Expert éminent sénior
    Citation Envoyé par viry0ne Voir le message
    heu non le null est pas inutile parce que ça fait partie de la forme de la fonction ... (enfin de ce que j'ai compris).
    man pthread_create
    Ce que Bousk te dit, c'est que soit tu appelles la fonction avec ce paramètre à nul (typiquement donc ça donnerait pthread_create(&tid, NULL, runner, NULL)) et donc dans ce cas pas besoin de définir une variable qui ne servira pas ; soit tu l'appelles en lui passant l'adresse d'une variable définie et donc dans ce cas, cette adresse étant celle d'une variable existante, on sait avec certitude qu'elle ne peut pas être à nul.

    Citation Envoyé par viry0ne Voir le message
    Du coup je ne sais pas ce que ça fait si je n'avait pas mis null mais je suppose que ça ne compilerait pas (la variable n'étant pas déclarée, et si elle l'était aucune idée !)
    Effectivement sans définir la variable dont tu veux passer l'adresse, ça ne compile pas (c'est comme ça que je l'ai vu et non pas avec mon oeuil de lynx). Et à mon avis, si la variable est définie, alors comme la fonction reçoit son adresse elle peut la modifier pour y mettre des trucs à elle (hypothèse car je ne la connais pas).
    Toutefois si c'est ça, alors dans ce cas on a le droit de faire abstraction de la remarque de Bousk quand il dit qu'elle est initialisée avec des valeurs aléatoire car certes c'est vrai (une variable définie et non initialisée contient alors n'importe quoi) mais si cette fonction a comme effet colatéral de remplir cette variable, alors c'est pas vraiment grave (je suis partisan du "je n'initialise jamais un truc que je vais remplir plus tard" que je simplifie en "ne jamais remplir deux fois une variable sans la lire entre temps").
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  14. #14
    Membre chevronné
    Bonjour ,
    Citation Envoyé par dalfab Voir le message
    Oui, il ne faut pas confondre processus et threads.

    Après un fork() on a 2 processus pour lesquels initialement le fils est la copie à l'identique de son père (donc à cet instant toutes les variables ont été clonées et ont la même valeur. Si des fichiers ont été ouverts avant, les deux processus partent du même état fichier, ...) Après le fork() chacun vis sa vie sans aucun point commun donc aucun risque de collisions.

    Si l'on regarde vos deux différents postes, l'information est contradictoire et erronée, je m'explique : fork() effectue une copie quasi-identique et non identique à original; la différence entre quasi-identique contrairement à identique réside dans le fait que les processus à l'issue de l'appel fork() ont leurs propres espaces de données, environnement et descripteur de fichiers et donc ils sont indépendamment ordonnancés.
    Si vous partez du postulat qu'elles sont identiques et donc du même état de fichier, il y a un conflit/race condition, chose qui n'est pas souhaitable, car le but et d'avoir à l'issue de cet appel (fork()) deux processus bien distincts indépendants ne partageant pas ou ne pointant pas sur le même descripteur de fichier. Donc il est important de souligner que ce n'est pas parce qu'il y a eu une copie/clone que cela signifie qu'elles sont identiques. D'ailleurs, le système d'exploitation comprend très bien cela et attribue des données et des états différents pour les deux processus, tout comme le flux de contrôle. La preuve étant l'exemple classique de l'appels de fork() où l'on distingue bien que les données des deux processus sont différentes ce qui permet d'obtenir deux sorties différentes.


    Je vais également ajouter un point important sur ce qui a été dit ; il faut lire les avertissements ou les erreurs que le compilateur affiche : ici, vous avez deux erreurs "implicit declaration of function "fork" et "implicit declaration of function "wait"". Cela est dû au fait qu'il vous manque des en-têtes des fichiers suivants #include <pthread.h> , #include <sys/types.h> et #include <sys/wait.h>. Aussi, à l'avenir, il faut prendre l'habitude d'inclure ces en-têtes. Certains diront que la fonction fork() est incluse dans la bibliothèque C standard donc elle est implicitement liée aux fichiers lors de la compilation donc il n'y a pas de quoi à s'alarmer sauf que d'un autre côté, ils ont tort, car cela ne certifie en rien que la fonction soit appelée de manière correcte. Ce qui fait que votre application, même étant une application pédagogique/dans un avenir professionnel sera considéré comme étant une application non-conforme (ou dite tâchée à l'image des modules noyau dont la licence est propriétaire et qui tâchent les noyaux UNIX/Linux) en d'autres termes pour faire simple, cela signifie que l'application n'est pas en conformité avec la norme POSIX.

    à bientôt.



    Celui qui peut, agit. Celui qui ne peut pas, enseigne.
    Il y a deux sortes de savants: les spécialistes, qui connaissent tout sur rien,
    et les philosophes, qui ne connaissent rien sur tout.
    George Bernard Shaw

  15. #15
    Nouveau membre du Club
    Citation Envoyé par sambia39 Voir le message
    il faut prendre l'habitude d'inclure ces en-têtes
    Wow, alors là j'avoue qu'il faut que je relise ton message entier à tête reposée car ces explications sont complètes et un peu velues pour mon petit niveau, merci merci, je vais grâce à vous devenir une pro des fork et des thread

    Alors pour le coup effectivement, mes camarades et moi même avions noté que le code donné par l'enseignant n'était pas correct. Ayant bien ajouté les bibli qui vont bien dans mon code je vous ai transmis celui de l’énoncé et pas celui que j'ai compilé (sans erreurs).
    Toutes mes excuses car du coup ce n'est pas très correct mais cela à permis d'alimenter la conversation de manière fort intéressante

    Voici donc le code que j'ai exécuté :
    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
    #include <pthread.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <unistd.h>
    int value = 0;
    /* The thread will begin control in this function */
    void *runner(void *param) {
        value = 5;
        pthread_exit(0);
    }
    int main(int argc, char *argv[]) {
      int pid;
      pthread_t tid; /* the thread identifier */
      pid = fork();
      if (!pid) { /* Child process */
        pthread_create(&tid, NULL, runner, NULL);
        pthread_join(tid, NULL);
        printf("CHILD : value = %d\n", value); /* LINE C */
        printf("PID & TID : value = %d - %ld\n", pid, tid); /* LINE C */
      }
      else { /* Parent process */
        wait(NULL);
      //  pthread_join(tid, NULL); Processus infini !!!
        printf("PARENT: value = %d\n", value); /* LINE P */
        printf("PID & TID : value = %d - %ld\n", pid, tid); /* LINE C */
      }
    }


    Je pousse sans doute plus loin que ce qui est attendu par l'enseignant mais autant profiter de cette période de confinement pour m'attarder sur chaque ligne du code !
    Je vais continuer à creuser à la lumière de ce que vous m'avez apporté et je reviens vous embêter histoire de m'assurer que j'ai bien compris

    La bise à vous