web-dev-qa-db-fra.com

pthread_exit vs return

J'ai une fonction de coureur pthread joignable définie comme ci-dessous:

void *sumOfProducts(void *param)
{
...
pthread_exit(0);
}

Ce thread est censé rejoindre le thread principal.

Chaque fois que j'exécutais mon programme via Valgrind, j'obtenais les fuites suivantes:

LEAK SUMMARY:
   definitely lost: 0 bytes in 0 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 968 bytes in 5 blocks
        suppressed: 0 bytes in 0 blocks

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 10)

J'ai vérifié la page de manuel pour pthreads qui disait:

  The new thread terminates in one of the following ways:

   * It  calls  pthread_exit(3),  specifying  an exit status value that is
     available  to  another  thread  in  the  same  process   that   calls
     pthread_join(3).

   * It  returns  from  start_routine().   This  is  equivalent to calling
     pthread_exit(3) with the value supplied in the return statement.

   * It is canceled (see pthread_cancel(3)).

   * Any of the threads in the process calls exit(3), or the  main  thread
     performs  a  return  from main().  This causes the termination of all
     threads in the process.

Miraculeusement, lorsque j'ai remplacé pthread_exit () par une instruction return, les fuites ont dispar.

return(NULL);

Ma vraie question est à trois volets:

  1. Quelqu'un peut-il expliquer pourquoi la déclaration de retour n'a donné aucune fuite?
  2. Y a-t-il une différence fondamentale entre les deux déclarations, par rapport à la sortie des threads?
  3. Si oui, quand doit-on préférer l'un à l'autre?
37
user191776

Le scénario de test minimal suivant présente le comportement que vous décrivez:

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

void *app1(void *x)
{
    sleep(1);
    pthread_exit(0);
}

int main()
{
    pthread_t t1;

    pthread_create(&t1, NULL, app1, NULL);
    pthread_join(t1, NULL);

    return 0;
}

valgrind --leak-check=full --show-reachable=yes Montre 5 blocs alloués à partir de fonctions appelées par pthread_exit() qui sont non libérés mais toujours accessibles à la sortie du processus. Si la pthread_exit(0); est remplacée par return 0;, Les 5 blocs ne sont pas alloués.

Cependant, si vous testez la création et la jonction d'un grand nombre de threads, vous constaterez que la quantité de mémoire non libérée utilisée à la sortie pas augmente. Ceci, et le fait qu'il soit toujours accessible, indique que vous voyez juste une bizarrerie de l'implémentation de la glibc. Plusieurs fonctions glibc allouent de la mémoire avec malloc() la première fois qu'elles sont appelées, qu'elles conservent allouées pour le reste de la durée de vie du processus. glibc ne prend pas la peine de libérer cette mémoire à la sortie du processus, car elle sait que le processus est de toute façon détruit - ce serait juste une perte de cycles CPU.

41
caf

Je ne sais pas si cela vous intéresse toujours, mais je débogue actuellement une situation similaire. Threads qui utilisent pthread_exit oblige valgrind à signaler les blocs accessibles. La raison semble être assez bien expliquée ici:

https://bugzilla.redhat.com/show_bug.cgi?id=483821

Il semble essentiellement pthread_exit provoque un dlopen qui n'est jamais nettoyé explicitement à la fin du processus.

11
Steven S

Cela ressemble à appeler exit () (et, apparemment, pthread_exit ()) laisse les variables allouées automatiquement allouées. Vous devez soit retourner ou lancer afin de vous détendre correctement.

Par C++ valgrind fuites possibles sur la chaîne STL :

@Klaim: Je ne vois pas où ce document dit que je me trompe, mais si c'est le cas, alors c'est faux. Pour citer la norme C++ (§18.3/8): "Les objets automatiques ne sont pas détruits suite à l'appel de exit ()." - James McNellis 10 septembre 10 à 19:11

Puisque faire un "return 0" au lieu de "pthread_exit (0)" a semblé résoudre votre problème (et le mien .. merci), je suppose que le comportement est similaire entre les deux.

1
Dustin Oprea

J'ai l'expérience que valgrind a des difficultés à suivre le stockage alloué pour l'état des threads joignables. (Cela va dans le même sens que l'indique caf.)

Puisqu'il semble que vous renvoyiez toujours une valeur de 0 Je suppose que vous devez peut-être joindre vos discussions du point de vue de l'application? Si c'est le cas, envisagez de les lancer détachés dès le départ, cela évite l'allocation de cette mémoire.

L'inconvénient est que vous avez:

  1. pour implémenter votre propre barrière à la fin de votre main. Si vous connaissez le nombre de threads au préalable, un simple pthread_barrier Alloué statiquement suffirait.
  2. ou pour vous quitter main avec pthread_exit afin de ne pas tuer les autres threads en cours d'exécution qui ne sont peut-être pas encore terminés.
0
Jens Gustedt

Utilisez-vous réellement C++, par hasard? Pour clarifier - votre fichier source se termine par un .c extension, et vous la compilez avec gcc, pas g++?

Il semble raisonnablement probable que votre fonction alloue des ressources que vous prévoyez de nettoyer automatiquement au retour de la fonction. Objets C++ locaux comme std::vector ou std::string faites cela, et leurs destructeurs ne seront probablement pas exécutés si vous appelez pthread_exit, mais serait nettoyé si vous revenez.

Ma préférence est d'éviter les API de bas niveau telles que pthread_exit, et toujours simplement revenir de la fonction thread, si possible. Ils sont équivalents, sauf que pthread_exit est une construction de contrôle de flux de facto qui contourne le langage que vous utilisez, mais pas return.

0
Doug