Arrêt des unités d'exécution

Une unité d'exécution s'arrête automatiquement lorsqu'elle revient de sa routine de point d'entrée.

Une unité d'exécution peut également explicitement s'arrêter elle-même ou arrêter toute autre unité d'exécution du processus, à l'aide d'un mécanisme appelé annulation. Etant donné que toutes les unités d'exécution partagent le même espace de données, une unité d'exécution doit effectuer des opérations de nettoyage au moment de l'arrêt ; la bibliothèque d'unités d'exécution fournit des gestionnaires de nettoyage à cette fin.

Sortie d'une unité d'exécution

Un processus peut se fermer à tout moment lorsqu'une unité d'exécution appelle la sous-routine exit . De même, une unité d'exécution peut se fermer à tout moment en appelant la sous-routine pthread_exit .

L'appel de la sous-routine exit met fin à l'ensemble du processus, y compris à toutes ses unités d'exécution. Dans un programme à unités d'exécution multiples, la sous-routine exit ne doit être utilisée que lorsque l'ensemble du processus doit être arrêté ; par exemple, en cas d'erreur irrémédiable. La sous-routine pthread_exit doit être préférée, même pour la sortie de l'unité d'exécution initiale.

L'appel de la sous-routine pthread_exit met fin à l'unité d'exécution appelante. Le paramètre status est sauvegardé par la bibliothèque et peut être utilisé lors de l'association de l'unité d'exécution terminée. L'appel de la sous-routine pthread_exit est similaire, mais non identique, au renvoi à partir de la routine initiale de l'unité d'exécution. Le résultat du renvoi à partir de la routine initiale de l'unité d'exécution dépend de l'unité d'exécution:

  • Le renvoi à partir de l'unité d'exécution initiale appelle implicitement la sous-routine exit , ce qui met fin à toutes les unités d'exécution du processus.
  • Le renvoi à partir d'une autre unité d'exécution appelle implicitement la sous-routine pthread_exit . La valeur renvoyée a le même rôle que le paramètre status de la sous-routine pthread_exit .

Pour éviter d'appeler implicitement la sous-routine exit , utilisez la sous-routine pthread_exit pour quitter une unité d'exécution.

La sortie de l'unité d'exécution initiale (par exemple, en appelant la sous-routine pthread_exit à partir de la routine main ) ne met pas fin au processus. Il arrête uniquement l'unité d'exécution initiale. Si l'unité d'exécution initiale est arrêtée, le processus sera arrêté à la fin de la dernière unité d'exécution. Dans ce cas, le code retour du processus est 0.

Le programme suivant affiche exactement 10 messages dans chaque langue. Pour ce faire, vous devez appeler la sous-routine pthread_exit dans la routine main après avoir créé les deux unités d'exécution et créer une boucle dans la routine Thread .

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */

void *Thread(void *string)

{
        int i;
 
        for (i=0; i<10; i++)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}

int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_exit(NULL);
}

La sous-routine pthread_exit libère toutes les données spécifiques à l'unité d'exécution, y compris la pile de l'unité d'exécution. Toute donnée allouée sur la pile devient invalide, car la pile est libérée et la mémoire correspondante peut être réutilisée par une autre unité d'exécution. Par conséquent, les objets de synchronisation d'unité d'exécution (mutex et variables de condition) alloués sur la pile d'une unité d'exécution doivent être détruits avant que l'unité d'exécution n'appelle la sous-routine pthread_exit .

Contrairement à la sous-routine exit , la sous-routine pthread_exit ne nettoie pas les ressources système partagées entre les unités d'exécution. Par exemple, les fichiers ne sont pas fermés par la sous-routine pthread_exit , car ils peuvent être utilisés par d'autres unités d'exécution.

Annulation d'une unité d'exécution

Le mécanisme d'annulation d'unité d'exécution permet à une unité d'exécution de mettre fin à l'exécution de toute autre unité d'exécution du processus de manière contrôlée. L'unité d'exécution cible (c'est-à-dire celle qui est en cours d'annulation) peut suspendre les demandes d'annulation en attente de plusieurs manières et effectuer un traitement de nettoyage spécifique à l'application lorsque la notification d'annulation est traitée. Lorsqu'elle est annulée, l'unité d'exécution appelle implicitement la sous-routine pthread_exit ((void *) -1) .

L'annulation d'une unité d'exécution est demandée en appelant la sous-routine pthread_cancel . Lorsque l'appel est renvoyé, la demande a été enregistrée, mais l'unité d'exécution est peut-être toujours en cours d'exécution. L'appel de la sous-routine pthread_cancel échoue uniquement lorsque l'ID d'unité d'exécution spécifié n'est pas valide.

Etat et type d'annulabilité

L'état d'annulation et le type d'une unité d'exécution déterminent l'action effectuée à la réception d'une demande d'annulation. Chaque unité d'exécution contrôle son propre état et type d'annulation avec les sous-routines pthread_setcancelstate et pthread_setcanceltype .

Les états d'annulation et les types d'annulation possibles suivants conduisent à trois cas possibles, comme indiqué dans le tableau suivant.
Etat d'annulation Type d'annulabilité Cas résultant
Désactivé Any (le type est ignoré) Possibilité d'annulation désactivée
Activé Deferred Annulabilité différée
Activé Asynchrone Annulabilité asynchrone
Les cas possibles sont décrits comme suit:
  • Annulation désactivée. Toute demande d'annulation est en attente, jusqu'à ce que l'état d'annulation soit modifié ou que l'unité d'exécution soit arrêtée d'une autre manière.

    Une unité d'exécution doit désactiver l'annulation uniquement lorsqu'elle effectue des opérations qui ne peuvent pas être interrompues. Par exemple, si une unité d'exécution effectue des opérations de sauvegarde de fichiers complexes (telles qu'une base de données indexée) et qu'elle est annulée au cours de l'opération, les fichiers peuvent être laissés dans un état incohérent. Pour éviter cela, l'unité d'exécution doit désactiver l'annulation lors des opérations de sauvegarde de fichier.

  • Annulation différée. Toute demande d'annulation est définie en attente, jusqu'à ce que l'unité d'exécution atteigne le point d'annulation suivant. Il s'agit de l'état d'annulation par défaut.

    Cet état d'annulation garantit qu'une unité d'exécution peut être annulée, mais limite l'annulation à des moments spécifiques de l'exécution de l'unité d'exécution, appelés points d'annulation. Une unité d'exécution annulée sur un point d'annulation laisse le système dans un état sûr ; cependant, les données utilisateur peuvent être incohérentes ou des verrous peuvent être maintenus par l'unité d'exécution annulée. Pour éviter ces situations, utilisez des gestionnaires de nettoyage ou désactivez l'annulabilité dans les régions critiques. Pour plus d'informations, voir Utilisation des gestionnaires de nettoyage .

  • Annulation asynchrone. Toute demande d'annulation est traitée immédiatement.

    Une unité d'exécution qui est annulée de manière asynchrone alors qu'elle détient des ressources peut quitter le processus, ou même le système, dans un état à partir duquel il est difficile ou impossible d'effectuer une reprise. Pour plus d'informations sur la sécurité async-cancel, voir Async-Cancel Safety.

Sécurité Async-cancel

Une fonction est dite async-cancel safe si elle est écrite de sorte que l'appel de la fonction avec l'annulation asynchrone activée n'entraîne pas l'altération d'une ressource, même si une demande d'annulation est envoyée à une instruction arbitraire.

Toute fonction qui obtient une ressource en tant qu'effet secondaire ne peut pas être sécurisée par async-cancel. Par exemple, si la sous-routine malloc est appelée avec la fonction d'annulation asynchrone activée, elle peut acquérir la ressource avec succès, mais lorsqu'elle est renvoyée à l'appelant, elle peut agir sur une demande d'annulation. Dans un tel cas, le programme n'aurait aucun moyen de savoir si la ressource a été acquise ou non.

C'est la raison pour laquelle la plupart des routines de bibliothèque ne peuvent pas être considérées comme async-cancel safe. Il est recommandé d'utiliser l'annulabilité asynchrone uniquement si vous êtes sûr d'effectuer uniquement des opérations qui ne contiennent pas de ressources et uniquement pour appeler des routines de bibliothèque qui sont sécurisées par l'annulation asynchrone.

Les sous-routines suivantes sont async-cancel safe ; elles garantissent que l'annulation sera traitée correctement, même si l'annulabilité asynchrone est activée:

  • pthread_cancel
  • pthread_setcancelstate
  • pthread_setcanceltype

Une alternative à l'annulabilité asynchrone consiste à utiliser l'annulabilité différée et à ajouter des points d'annulation explicites en appelant la sous-routine pthread_testcancel .

Points d'annulation

Les points d'annulation sont des points à l'intérieur de certaines sous-routines où une unité d'exécution doit agir sur toute demande d'annulation en attente si l'annulabilité différée est activée. Toutes ces sous-routines peuvent bloquer l'unité d'exécution appelante ou calculer indéfiniment.

Un point d'annulation explicite peut également être créé en appelant la sous-routine pthread_testcancel . Cette sous-routine crée simplement un point d'annulation. Si l'annulation différée est activée et qu'une demande d'annulation est en attente, la demande est traitée et l'unité d'exécution est arrêtée. Sinon, la sous-routine est simplement renvoyée.

D'autres points d'annulation se produisent lors de l'appel des sous-routines suivantes:

  • pthread_cond_wait
  • pthread_cond_timedwait
  • pthread_join

Les sous-routines pthread_mutex_lock et pthread_mutex_trylock ne fournissent pas de point d'annulation. Si tel était le cas, toutes les fonctions appelant ces sous-routines (et de nombreuses fonctions le font) fourniraient un point d'annulation. Le fait d'avoir trop de points d'annulation rend la programmation très difficile, nécessitant soit beaucoup de désactivation et de restauration de l'annulabilité, soit des efforts supplémentaires pour essayer d'organiser un nettoyage fiable à chaque endroit possible. Pour plus d'informations sur ces sous-routines, voir Utilisation de Mutexes.

Les points d'annulation se produisent lorsqu'une unité d'exécution exécute les fonctions suivantes:

Fonction
aio_suspendre fermer
création fcntl
fsync getmsg
getpmsg verrouiller
mq_réception mq_envoi
msgrcv msgsnd
msync nanosommeil
Ouvrir suspendre
Poll Propagation
pthread_cond_timedwait pthread_cond_wait
pthread_join pthread_testcancel
msgPutp pwrite
read lirev
Sélectionner sem_attente
sigpause sigsuspension
attente de sigtimedwait attente de signature
sigwaitinfo mise en veille
Système tcdrain
sommeil wait
wait3 waitid
ID_attente écriture
écriturev

Un point d'annulation peut également se produire lorsqu'une unité d'exécution exécute les fonctions suivantes:

Fonction Fonction Fonction
catclose catgets catopen
rép_fermé journal de fermeture ctermid
ferme_bdm supprimer_base_de_données extraction_dbm
clé_next_dbm dbm_open dbm_store (Magasin de données)
dlclose dlOuvrir terminaison
finpwent fwprintf fwrite
fwscanf getc getc_déverrouillé
getchar getchar_déverrouillé getcwd
obtenir la date méthode getgrent getgrgid
getgrgid_r getgrnam getgrnam_r
méthode getlogin getlogin_r fenêtre en incrustation
printf putc putc_déverrouillé
putchar putchar_déverrouillé insertions
ligne de transaction mettre putwc
putwchar réaddir readdir_r
Retrait changement de nom Rebobinage
endutxent fclose fcntl
commande fflush fgetc fgetpos
fgets fgetwc fgetws
fopen fprintf fputc
fputs getpwent getpwnam
getpwnam_r ID utilisateur getpwuid getpwuid_r
obtient getutxent getutxid
getutxline méthode d'accès get getwc
getwchar getwd (mot de passe) rérép_vent
scanf rép_install semop
setgrent (setgrent) setpwent setutxent
erreur syslog fichierTmps
tmpnam ttyname nom_tty_r
fputwc fputws fread
ouverture libre fscanf fseek
fseeko fsetpos ftell
violoncelle ftello ftw glob
iconv_close iconv_open ioctl
lseek mkstemp nftw
opendir openlog pclose
erreur ungetc ungetwc
supprimer le lien vfprintf vfwprintf
vprintf vwprintf wprintf
wscanf

Les effets secondaires d'une action sur une requête d'annulation suspendue lors d'un appel d'une fonction sont les mêmes que les effets secondaires qui peuvent être observés dans un programme à une seule unité d'exécution lorsqu'un appel à une fonction est interrompu par un signal et que la fonction donnée renvoie [ EINTR ]. Ces effets secondaires se produisent avant que les gestionnaires de nettoyage d'annulation ne soient appelés.

Chaque fois qu'une unité d'exécution a activé l'annulation et qu'une demande d'annulation a été effectuée avec cette unité d'exécution comme cible et que l'unité d'exécution appelle la sous-routine pthread_testcancel , la demande d'annulation est traitée avant que la sous-routine pthread_testcancel ne soit renvoyée. Si l'annulation d'une unité d'exécution est activée et que l'unité d'exécution a une demande d'annulation asynchrone en attente et que l'unité d'exécution est suspendue à un point d'annulation en attente d'un événement, la demande d'annulation est traitée. Toutefois, si l'unité d'exécution est suspendue à un point d'annulation et que l'événement qu'elle attend se produit avant que la demande d'annulation ne soit traitée, la séquence d'événements détermine si la demande d'annulation est traitée ou si la demande reste en attente et l'unité d'exécution reprend son exécution normale.

Exemple d'annulation

Dans l'exemple suivant, les deux unités d'exécution "writer" sont annulées après 10 secondes et après avoir écrit leur message au moins cinq fois.

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */
#include <unistd.h>     /* include file for sleep()            */
void *Thread(void *string)
{
        int i;
        int o_state;
 
        /* disables cancelability */
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &o_state);
 
        /* writes five messages */
        for (i=0; i<5; i++)
                printf("%s\n", (char *)string);
 
        /* restores cancelability */
        pthread_setcancelstate(o_state, &o_state);
 
        /* writes further */
        while (1)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}
int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        /* creates both threads */
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                return -1;
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                return -1;
  
        /* sleeps a while */
        sleep(10);
 
        /* requests cancelation */
        pthread_cancel(e_th);
        pthread_cancel(f_th);
 
        /* sleeps a bit more */
        sleep(10);
        pthread_exit(NULL);
}

Sous-routines de temporisation et de veille

Les routines de temporisation s'exécutent dans le contexte de l'unité d'exécution appelante. Ainsi, si un temporisateur expire, la fonction de temporisation du programme de surveillance est appelée dans le contexte de l'unité d'exécution. Lorsqu'un processus ou une unité d'exécution passe en mode veille, il abandonne le processeur. Dans un processus à unités d'exécution multiples, seule l'unité d'exécution appelante est mise en veille.

Utilisation des gestionnaires de nettoyage

Les gestionnaires de nettoyage fournissent un mécanisme portable permettant de libérer des ressources et de restaurer des invariants lorsqu'une unité d'exécution se termine.

Appel des gestionnaires de nettoyage

Les gestionnaires de nettoyage sont spécifiques à chaque unité d'exécution. Une unité d'exécution peut avoir plusieurs gestionnaires de nettoyage ; ils sont stockés dans une pile LIFO (last-in, first-out) spécifique à l'unité d'exécution. Les gestionnaires de nettoyage sont tous appelés dans les cas suivants:

  • L'unité d'exécution est renvoyée à partir de sa routine de point d'entrée.
  • L'unité d'exécution appelle la sous-routine pthread_exit .
  • L'unité d'exécution agit sur une demande d'annulation.

Un gestionnaire de nettoyage est inséré dans la pile de nettoyage par la sous-routine pthread_cleanup_push . La sous-routine pthread_cleanup_pop fait apparaître le gestionnaire de nettoyage le plus haut de la pile et l'exécute éventuellement. Utilisez cette sous-routine lorsque le gestionnaire de nettoyage n'est plus nécessaire.

Le gestionnaire de nettoyage est une routine définie par l'utilisateur. Il comporte un paramètre, un pointeur void, spécifié lors de l'appel de la sous-routine pthread_cleanup_push . Vous pouvez spécifier un pointeur vers certaines données dont le gestionnaire de nettoyage a besoin pour effectuer son opération.

Dans l'exemple suivant, une mémoire tampon est allouée pour l'exécution d'une opération. Lorsque l'annulation différée est activée, l'opération peut être arrêtée à n'importe quel point d'annulation. Dans ce cas, un gestionnaire de nettoyage est établi pour libérer la mémoire tampon.

/* the cleanup handler */
 
cleaner(void *buffer)
 
{
        free(buffer);
}

/* fragment of another routine */
...
myBuf = malloc(1000);
if (myBuf != NULL) {
        
        pthread_cleanup_push(cleaner, myBuf);
 
        /*
         *       perform any operation using the buffer,
         *       including calls to other functions
         *       and cancelation points
         */
        
        /* pops the handler and frees the buffer in one call */
        pthread_cleanup_pop(1);
}

L'utilisation de l'annulation différée garantit que l'unité d'exécution n'agira pas sur les demandes d'annulation entre l'allocation de la mémoire tampon et l'enregistrement du gestionnaire de nettoyage, car ni la sous-routine malloc ni la sous-routine pthread_cleanup_push ne fournissent de point d'annulation. Lorsque le gestionnaire de nettoyage est lancé, il est exécuté, ce qui libère la mémoire tampon. Des programmes plus complexes peuvent ne pas exécuter le gestionnaire lors de son envoi, car le gestionnaire de nettoyage doit être considéré comme une "sortie d'urgence" pour la partie protégée du code.

Equilibrage des opérations push et pop

Les sous-routines pthread_cleanup_push et pthread_cleanup_pop doivent toujours apparaître par paires dans la même portée lexicale, c'est-à-dire dans la même fonction et le même bloc d'instructions. Ils peuvent être considérés comme des parenthèses gauches et droites encadrant une partie protégée du code.

La raison de cette règle est que sur certains systèmes, ces sous-routines sont implémentées en tant que macros. La sous-routine pthread_cleanup_push est implémentée en tant qu'accolade gauche, suivie d'autres instructions:
#define pthread_cleanup_push(rtm,arg) { \
         /* other statements */
La sous-routine pthread_cleanup_pop est implémentée comme accolade droite, en suivant les autres instructions:
#define pthread_cleanup_pop(ex) \
         /* other statements */  \
}

Respectez la règle d'équilibrage pour les sous-routines pthread_cleanup_push et pthread_cleanup_pop afin d'éviter les erreurs de compilateur ou le comportement inattendu de vos programmes lors du portage vers d'autres systèmes.

Dans " AIX, les sous-programmes " pthread_cleanup_push et " pthread_cleanup_pop sont des routines de bibliothèque et peuvent être déséquilibrés dans le même bloc d'instructions. Toutefois, ils doivent être équilibrés dans le programme, car les gestionnaires de nettoyage sont empilés.

Sous-routine Descriptif
pthread_attr_destroy Supprime un objet d'attributs d'unité d'exécution.
pthread_attr_getdetachstate Renvoie la valeur de l'attribut detachstate d'un objet d'attributs d'unité d'exécution.
pthread_attr_init Crée un objet d'attributs d'unité d'exécution et l'initialise avec les valeurs par défaut.
pthread_cancel Demande l'annulation d'une unité d'exécution.
pthread_cleanup_pop Supprime et exécute éventuellement la routine en haut de la pile de nettoyage de l'unité d'exécution appelante.
pthread_cleanup_push Envoie une routine sur la pile de nettoyage de l'unité d'exécution appelante.
pthread_create Crée une nouvelle unité d'exécution, initialise ses attributs et la rend exécutable.
pthread_equal Compare deux ID d'unité d'exécution.
exit pthread_exit Met fin à l'unité d'exécution appelante.
pthread_self Renvoie l'ID de l'unité d'exécution appelante.
pthread_setcancelstate Définit l'état d'annulation de l'unité d'exécution appelante.
pthread_setcanceltype Définit le type d'annulabilité de l'unité d'exécution appelante.
annulation_test_pthread_annulation Crée un point d'annulation dans l'unité d'exécution appelante.