pthreads en C - pthread_exit
Pour une raison que je pense que le fait d'appeler pthread_exit(NULL)
à la fin d'une fonction principale de garantir que tous les threads en cours d'exécution (au moins créé dans la fonction principale) pour la fin de course avant main
la sortie. Cependant lorsque j'exécute ce code ci-dessous sans appeler les deux pthread_join
fonctions (à la fin de main
) explicitement j'obtiens une erreur de segmentation, ce qui semble se produire en raison de la main
fonction a été fermée avant que les deux threads terminer leur travail, et donc la char tampon n'est plus disponible. Cependant, quand je inclure ces deux pthread_join
appels de fonction à la fin de main
il fonctionne comme il se doit. Pour garantir que main
ne sera pas sortie avant que tous les threads en cours d'exécution ont fini, est-il nécessaire de faire appel à pthread_join
explicitement pour tous les threads initialisées directement dans main
?
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>
#define NUM_CHAR 1024
#define BUFFER_SIZE 8
typedef struct {
pthread_mutex_t mutex;
sem_t full;
sem_t empty;
char* buffer;
} Context;
void *Reader(void* arg) {
Context* context = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(&context->full);
pthread_mutex_lock(&(context->mutex));
char c = context->buffer[i % BUFFER_SIZE];
pthread_mutex_unlock(&(context->mutex));
sem_post(&context->empty);
printf("%c", c);
}
printf("\n");
return NULL;
}
void *Writer(void* arg) {
Context* context = (Context*) arg;
for (int i = 0; i < NUM_CHAR; ++i) {
sem_wait(&context->empty);
pthread_mutex_lock(&(context->mutex));
context->buffer[i % BUFFER_SIZE] = 'a' + (rand() % 26);
float ranFloat = (float) rand() / RAND_MAX;
if (ranFloat < 0.5) sleep(0.2);
pthread_mutex_unlock(&(context->mutex));
sem_post(&context->full);
}
return NULL;
}
int main() {
char buffer[BUFFER_SIZE];
pthread_t reader, writer;
Context context;
srand(time(NULL));
int status = 0;
status = pthread_mutex_init(&context.mutex, NULL);
status = sem_init(&context.full,0,0);
status = sem_init(&context.empty,0, BUFFER_SIZE);
context.buffer = buffer;
status = pthread_create(&reader, NULL, Reader, &context);
status = pthread_create(&writer, NULL, Writer, &context);
pthread_join(reader,NULL); //This line seems to be necessary
pthread_join(writer,NULL); //This line seems to be necessary
pthread_exit(NULL);
return 0;
}
Si c'est le cas, comment pourrais-je gérer le cas où l'abondance de l'identique fils (comme dans le code ci-dessous) sera créé en utilisant le même identifiant de thread? Dans ce cas, comment puis-je m'assurer que tous les threads ont fini avant main
issues de secours? Ai-je vraiment besoin de garder un tableau de NUM_STUDENTS pthread_t
identifiants pour être capable de faire cela? Je suppose que je pourrais le faire en permettant à l'Étudiant de fils de signal d'un sémaphore, puis laissez le main
fonction d'attente sur le sémaphore, mais il n'y a vraiment pas de moyen plus facile de faire cela?
int main()
{
pthread_t thread;
for (int i = 0; i < NUM_STUDENTS; i++)
pthread_create(&thread,NULL,Student,NULL); //Threads
//Make sure that all student threads have finished
exit(0);
}
sleep()
prend un argument entier; votre sleep(0.2)
est équivalent à sleep(0)
en raison de la conversion implicite. Si vous avez besoin d'un sous-deuxième intervalle du rêve, de l'utilisation (norme POSIX) nanosleep().OriginalL'auteur Siggi | 2010-07-25
Vous devez vous connecter pour publier un commentaire.
pthread_exit()
est une fonction appelée par un thread pour mettre fin à sa propre exécution. Pour la situation que vous avez donné, il s'agit de ne pas être appelés à partir de votre programme principal fil.Comme vous l'avez compris,
pthread_join()
est le bon moyen d'attendre l'achèvement d'un recrutables fil demain()
.Aussi comme vous l'avez compris, vous devez maintenir la valeur retournée par
pthread_create()
de passer àpthread_join()
.Ce que cela signifie est que vous ne pouvez pas utiliser le même
pthread_t
variable pour tous les threads que vous créez si vous avez l'intention d'utiliserpthread_join()
.Plutôt, construire un tableau de
pthread_t
de sorte que vous avez une copie de chaque thread ID.Il n'est certainement pas d'autorité. C'est plutôt juste des conseils pour quelqu'un de nouveau à pthreads. Peut-être que ma langue vient à travers dans le mauvais sens. Je vais régler ça. Je vous remercie pour cette.
Tout ce que j'ai jamais lu sur pthread_exit() suggère l'exact opposé de ce post; pthread_exit() devrait en effet être le dernier appel principal. "Main() explicitement appel pthread_exit() comme la dernière chose qu'il fait, main() bloquera et être maintenu en vie pour soutenir les threads créés jusqu'à ce qu'ils sont fait." Voir: manuel ou la Création et la Terminaison des Threads.
Ce qui vous manque, c'est le fait que sa main() ne peut pas quitter avant que son fils en raison des ressources allouées dans main (), s'de la pile et utilisé dans les filets. Si le main() est hors de portée avant le fils, il y aura un processus de collision. Si ce n'était pas vrai, alors pthread_exit() peut être utilisé avant que le fils se terminant. Le thread principal détruit sa pile avant le blocage de pthread_exit().
OriginalL'auteur Amardeep AC9MF
Indépendamment de savoir si le programme devrait ou ne devrait pas se terminer lorsque le thread principal appels
pthread_exit
,pthread_exit
ditEt aussi:
Puisque le contexte est une variable automatique de
main()
, votre code peut tomber avant même qu'elle arrive au point de tests de ce que vous voulez à l'essai...Context
etbuffer
avec statique de la durée de stockage.Ou mieux encore, en leur allouant
malloc
.OriginalL'auteur Steve Jessop
Une mini saga
Vous ne mentionnez pas l'environnement dans lequel vous exécutez le code d'origine. J'ai modifié votre code pour utiliser
nanosleep()
(puisque, comme je l'ai mentionné dans un commentaire à la question,sleep()
prend un entier et, par conséquent,sleep(0.2)
est équivalent àsleep(0)
), et compilé le programme sur mac os X 10.6.4.Sans contrôle d'erreur
Il fonctionne très bien; il a fallu environ 100 secondes pour s'exécuter avec 0,5 facteur de probabilité (comme vous pouvez vous attendre; j'ai changé de 0,05 à réduire la durée d'exécution est d'environ 10 secondes), et a généré une chaîne aléatoire - de temps en temps.
Parfois je n'ai rien eu, parfois, j'ai eu plus et parfois, j'ai eu moins de données. Mais je n'ai pas vu un core dump (même pas avec 'ulimit -c unlimited" pour permettre arbitrairement grande core dumps).
Finalement, j'ai appliqué des outils et j'ai pu voir que j'ai toujours eu de 1025 caractères (1024 généré plus d'un retour à la ligne), mais assez souvent, j'ai eu 1024 caractères ASCII NUL. Parfois, ils allaient apparaître dans le milieu, parfois au début, etc:
(Le " tpipe programme est comme 'tee', mais il écrit des tubes à la place de fichiers (et de la sortie standard, sauf si vous spécifiez l'option '-s'); 'vis' vient de 'L'Environnement UNIX" par Kernighan & Pike; " ww "est un " mot wrapper", mais il n'y a pas de mots ici, donc il force brute ajuste à la largeur 64.)
Le comportement que je voyais était très indéterminée - j'aimerais obtenir des résultats différents à chaque exécution. J'ai même remplacé les caractères aléatoires avec l'alphabet dans l'ordre ('a' + i % 26), et était encore en train de comportement étrange.
J'ai ajouté un peu de débogage de l'impression de code (et un compteur pour le contex), et il était clair que le sémaphore
context->full
ne fonctionnait pas correctement pour le lecteur - c'était d'être autorisés à entrer dans l'exclusion mutuelle avant que l'écrivain avait écrit quelque chose.Avec la vérification des erreurs
Quand j'ai ajouté la vérification des erreurs pour le mutex et sémaphore opérations, j'ai constaté que:
Donc, l'étrange sorties sont parce que MacOS X ne permet pas de mettre en œuvre
sem_init()
. C'est bizarre; lesem_wait()
fonction échoue avec errno = 9 (EBADF le "Mauvais descripteur de fichier'); j'ai ajouté le vérifie en premier. Puis j'ai vérifié l'initialisation...À l'aide de sem_open() au lieu de sem_init()
La
sem_open()
appels à réussir, ce qui semble bon (noms"/full.sem"
et"/empty.sem"
, drapeaux O_CREAT, mode valeurs de 0444, 0600, 0700 à des moments différents, et les valeurs initiales 0 et BUFFER_SIZE, comme avecsem_init()
). Malheureusement, la premièresem_wait()
ousem_post()
opération échoue avec errno = 9 (EBADF le "Mauvais descripteur de fichier') de nouveau.La morale
pthread_join()
appels comportement.sem_t
fonctions peut être interrompue par les gestionnaires de signaux (e.g I/O) et il peut avoirEINTR
danserrno
dans ce cas. Si oui, vous devez capturer l'erreur de toussem_t
fonctions, quelle que soit l'interface sur laquelle vous avez choisi de les créer.OriginalL'auteur Jonathan Leffler
Il n'est pas nécessaire pour appeler
pthread_join(reader,NULL);
siContext
etbuffer
sont déclarées avec statique de la durée de stockage (comme l'a déjà souligné Steve Jessop, la caf et David Schwartz).Déclarant
Context
etbuffer
statique rend également nécessaire de changerContext *context
àContext *contextr
ouContext *contextw
respectivement.En outre, la réécriture suivante appelé
pthread_exit.c
remplacesem_init()
avecsem_open()
et utilisenanosleep()
(comme suggéré par Jonathan Leffler).pthread_exit
a été testé sur Mac OS X 10.6.8 et n'a pas de sortie du tout ASCII NUL caractères.OriginalL'auteur chad
pthread_join()
est la façon standard d'attendre que l'autre thread pour terminer, je voudrais tenir à cela.Alternativement, vous pouvez créer un compteur de thread et ont des enfants tous les threads de l'incrémenter de 1 à démarrer, puis décrémenter par 1 quand ils ont fini (avec le bon verrouillage de cours), puis avoir votre
main()
attendre pour ce compteur de hit 0. (pthread_cond_wait()
serait mon choix).OriginalL'auteur m1tk4
Par normal
pthread
sémantique, comme l'enseigne par exemple ici, votre idée de départ semble être confirmé:Cependant, je ne suis pas sûr que cela fait partie de la norme POSIX threads standard ou juste une commune, mais pas universelle "nice to have" add-on friandise (je sais que certaines implémentations ne respectent pas cette contrainte -- je ne sais pas si ces implémentations sont néanmoins être considérée comme compatible avec le standard!-). Donc je vais avoir à se joindre à la prudence chœur de recommander l'adhésion de chaque fil que vous avez besoin de mettre fin, juste pour être sur le côté sûr, -- ou, comme Jon Postel mettre dans le contexte du protocole TCP/IP implémentations:
un "principe de robustesse" qui devrait être utilisé de façon plus large que dans le protocole TCP/IP;-).
auto
variables dansmain()
.OriginalL'auteur Alex Martelli
pthread_exit(3)
quitte le thread qui appelle (mais pas la totalité du processus si d'autres threads sont toujours en cours d'exécution). Dans votre exemple, d'autres threads à utiliser des variables surmain
's de la pile, donc quandmain
's thread existe et que sa pile est détruit, ils accès non mappés en mémoire, donc l'erreur de segmentation.Utiliser la bonne
pthread_join(3)
technique comme suggéré par d'autres, ou de déplacer des variables partagées en mémoire statique.OriginalL'auteur Nikolai Fetissov
Lorsque vous passez un fil un pointeur vers une variable, vous devez vous assurer que la durée de vie de cette variable est au moins aussi longtemps que le fil de la tentative d'accès à cette variable. Vous passez les fils des pointeurs vers
buffer
etcontext
qui sont allouées sur la pile à l'intérieur demain
. Dès quemain
sorties, ces variables ont cessé d'exister. Si vous ne pouvez pas quitter d'main
jusqu'à ce que vous confirmer que les fils n'ont plus besoin de l'accès à ces pointeurs.95% du temps, le correctif de ce problème est de suivre ce modèle simple:
Malheureusement, cela ne fonctionne pas bien pour les objets partagés par deux ou plusieurs threads. Dans ce cas, vous pouvez mettre un nombre d'utilisations et d'un mutex à l'intérieur de l'objet parameter. Chaque thread peut décrémenter le nombre d'utilisations sous la protection de l'mutex quand c'est fait. Le fil qui descend l'utilisation décompte à zéro libère l'objet.
Vous aurez besoin de faire cela pour les deux
buffer
etcontext
. Définir le nombre d'utilisations de2
et puis de passer un pointeur vers cet objet à deux fils.OriginalL'auteur David Schwartz
pthread_join
effectue les opérations suivantes :Cependant, vous pouvez obtenir le même en utilisant un poids léger boucle qui permettra d'éviter la
exe
de sortie. Dans Glib ceci est réalisé en créant un GMainLoop, dans Gtk+, vous pouvez utiliser le gtk_main.Après l'achèvement de threads que vous avez à quitter la boucle principale ou appelez
gtk_exit
.Sinon, vous pouvez créer votre propre liste d'attente de la fonctionnalité en utilisant une combinaison de sockets,des tuyaux et sélectionnez appel système, mais ce n'est pas obligatoire et peut être considéré comme un exercice pour la pratique.
OriginalL'auteur Praveen S