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 .
| 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 |
- 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.
#define pthread_cleanup_push(rtm,arg) { \
/* other statements */#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. |