Gestion des processus
Création des processus légers (threads)
Mr: EZZATI
1
Plan
Introduction sur les Threads
Threads en mode utilisateur et threads en mode noyau
Implémentation des threads sous linux
Création des threads
Synchronisation des threads
Les identifiants des threads
Les attributs des threads
Threads joingnables et threads détachés
Annulation des threads
Section critiques des threads
Gestion des signaux sur les threads
l’Appel Système Clone
Conclusion
Introduction
Les threads appelés aussi « processus légers » sont des
mécanismes permettant à un programme de faire plus
d'une chose à la fois, ils sont similaires aux processus
puisqu’ils représentent tous les deux l’exécution d’un
ensemble d’instructions du langage machine d’un
processeur .
Du point de vue de l’utilisateur ,ces exécutions semblent
se dérouler en parallèle . Toutefois chaque processus
possède ses propres ressources (fichiers, mémoires, etc.),
les processus légers appartenant au même processus
père partagent une grande partie de leurs ressources.
Les threads en mode (utilisateur & noyau )
Les threads en mode utilisateur
Les threads en mode utilisateur vivent entièrement à l’intérieur d’un programme et
de ses bibliothèques. Dans ce modèle, le Système d’exploitation ne sait rien
des threads. Pour ce qu’il en voit, votre processus utilisant les threads n’est qu’un
processus comme les autres.
C’est la façon la plus facile d’implémenter les threads. Mais il y a un gros
désavantage, puisque si un des threads bloque, ils bloquent tous au sein du
processus qui les a lancés. Le noyau constante seulement qu’un processus a bloqué.
Parmi les activités bloquantes courantes, on trouve la plupart des appels système,
des E/S, et des commandes comme sleep().
Les threads en mode (utilisateur & noyau )
Les threads du noyau
Les threads du noyau sont l’étape suivante dans l’évolution des threads.
Le Système d’Exploitation connaît des threads gérés par le noyau et en tient
compte. La principale différence entre un thread du noyau et un thread en mode
utilisateur est le blocage
Avec les threads du noyau, un thread peut bloquer sans que les
autres threads bloquent. Ce n’est pas le cas avec les threads en mode
utilisateur, où le noyau bloque au niveau du processus et pas au niveau
du thread.
C’est un grand pas en avant, qui peut donner à un programme utilisant
les threads un bonus de performance sur les programmes ne les utilisant pas.
Les threads qui bloquent au cours d’E/S, par exemple, ne bloqueront pas
les threads qui font autre chose.
Les threads en mode (utilisateur & noyau )
Les threads multiprocesseurs du noyau sont l’étape
finale dans l’évolution du support pour les threads.
Avec ce modèle, sur une machine à plusieurs
processeurs, le Système d’exploitation peut faire tourner
simultanément plusieurs threads sur plusieurs
processeurs.
Cela peut améliorer sérieusement les performances
d’un programme utilisant les threads, puisque plusieurs
s’exécuteront en même temps. En retour, cependant,
tous les problèmes de synchronisation qui ne se
manifestaient pas avec les threads du noyau vont
surgir
Processus Unix/Linux
Threads au sein d’un processus
Unix/Linux
Implémentation des Threads sous GNU/Linux
Nous avons vu comment un programme peut créer un
processus fils avec fork(). Celui-ci exécute
immédiatement le programme de son père, la mémoire
virtuelle, les descripteurs de fichiers, etc. de son père
étant copiés.
Le processus fils peut modifier sa mémoire, fermer les
descripteurs de fichiers sans que cela affecte son père,
et vice versa.
Implémentation des Threads sous GNU/Linux
Lorsqu'un programme crée un nouveau thread, par contre, rien
n'est copié. Le thread créateur et le thread créé partagent tous deux
le même espace mémoire, les mêmes descripteurs de fichiers et
autres ressources
Si un thread modifie la valeur d'une variable, par exemple, l'autre
thread verra la valeur modifiée. De même, si un thread ferme un
descripteur de fichier, les autres threads ne peuvent plus lire ou
écrire dans ce fichier.
Comme un processus et tous ses threads ne peuvent exécuter qu'un
seul programme à la fois, si un thread au sein d'un processus appelle
une des fonctions exec*(), tous les autres threads se terminent.
Implémentation des Threads sous GNU/Linux
Lancez le programme en arrière-plan puis invoquez ps x pour
afficher vos processus en cours d'exécution. Voici à quoi la sortie
pourrait ressembler:
Création de Threads
Chaque thread d'un processus est caractérisé par un identifiant de
thread. Lorsque vous manipulez les identifiants de threads dans des
programmes C ou C++, veillez à utiliser le type pthread_t.
Exemple : pthread_t thread_id;
Lors de sa création, chaque thread exécute une fonction de thread,
il s'agit d'une fonction ordinaire contenant le code que doit
exécuter le thread .
Lorsque la fonction se termine, le thread se termine également.
Sous GNU/Linux, les fonctions de thread ne prennent qu'un seul
paramètre de type void* et ont un type de retour void*.
void* print_xs (void* arg) {/* le code que doit exécuter le thread */}
Création de Threads
La fonction pthread_create crée un nouveau thread. Voici les
paramètres dont elle a besoin ,voyons l’exemple suivant:
pthread_create (&thread_id, NULL, &print_xs, NULL);
L'argument de la fonction thread est une méthode pratique
pour passer des données à un thread. Comme son type
est void*, cependant, vous ne pouvez pas passer beaucoup de
données directement en l'utilisant. Une technique couramment
utilisée est de définir une structure de données pour chaque
argument de thread.
Une solution possible de faire attendre un thread à la fin d’un autre
Synchroniser des Threads
est d’utiliser une fonction similaire à wait(&retour), qui attend la fin
d'un thread au lieu de celle d'un processus.
Cette fonction est pthread_join(id_t, &retour),qui prend deux
arguments: l'identifiant du thread à attendre et un pointeur vers une
variable void* qui recevra la valeur de retour du thread s'étant
terminé.
Si le second argument que vous passez à pthread_join n'est
pas NULL, la valeur de retour du thread sera stockée à
l'emplacement pointé par cet argument.
Les Identifiants de Thread
La fonction pthread_self renvoie l'identifiant du thread depuis
lequel elle a été appelée elle est équivalente à getpid() . Cet
identifiant de thread peut être comparé à un autre en utilisant la
fonction pthread_equal.
Ces fonctions peuvent être utiles pour déterminer si un identifiant de
thread particulier correspond au thread courant.
Par exemple, un thread ne doit jamais appeler pthread_join pour
s'attendre lui-même (dans ce cas, pthread_join renverrait le code
d'erreur EDEADLK).
if(!pthread_equal(pthread_self(),other_thread))
pthread_join (other_thread, NULL);
Les Attributs de Thread
Les attributs de thread proposent un mécanisme pour contrôler
finement le comportement de threads individuels.
Si vous passez un pointeur nul, les attributs par défaut sont utilisés
pour configurer le nouveau thread.
Pour indiquer des attributs de thread personnalisés ,on doit suivre les
étapes suivants:
Créez un objet pthread_attr_t . La façon la plus simple de le faire est
de déclarer une variable automatique de ce type.
pthread_attr_t attr ;
Appelez pthread_attr_init en lui passant un pointeur vers cet objet.
Cela initialise les attributs à leur valeur par défaut.
pthread_attr_init (&attr);
Les Attributs de Thread
Modifiez l'objet d'attributs pour qu'ils contiennent les valeurs
désirées. pthread_attr_setdetachstate à voir plu tard;
Passez un pointeur vers l'objet créé lors de l'appel
à pthread_create.
pthread_create (&thread, &attr, &thread_function, NULL);
Appelez pthread_attr_destroy pour libérer l'objet d'attributs.
La variable pthread_attr_t n'est pas libérée en elle-même; elle
peut être réinitialisée avec pthread_attr_init.
pthread_attr_destroy (&attr);
Thread joignable ou détaché
Les ressources d'un thread joignable, comme pour un processus,
ne sont pas automatiquement libérées par GNU/Linux lorsqu'il se
termine. Au lieu de cela, l'état de sortie du thread reste dans le
système (un peu comme pour un processus zombie) jusqu'à ce
qu'un autre thread appelle pthread_join pour récupérer cette valeur
de retour.
Les ressources d'un thread détaché, au contraire, sont
automatiquement libérées lorsqu'il se termine. Cela impliquer qu'un
autre thread ne peut attendre qu'il se termine avec pthread_join ou
obtenir sa valeur de retour.
Pour paramétrer l'état de détachement dans un objet d'attributs de
thread, utilisez
pthread_attr_setdetachstate (&att , etat);
Annulation de Thread
Dans des circonstances normales, un thread se termine soit en
arrivant à la fin de la fonction de thread, soit en appelant la
fonction pthread_exit.
Pour annuler un thread, appelez pthread_cancel, en lui
passant l'identifiant du thread à annuler.
Un thread annulé peut être joint ultérieurement; en fait, vous
devriez joindre un thread annulé pour libérer ses ressources, à
moins que le thread ne soit détaché La valeur de retour d'un
thread annulé est la valeur spéciale
PTHREAD_CANCELED.
Annulation de Thread
Un thread peut se comporter de trois façons suivantes
face à l'annulation :
Il peut être annulable de façon asynchrone.
C'est-à-dire à n'importe quel moment de son exécution.
Il peut être annulable de façon synchrone.
le thread n'est annulé que lors qu'il atteint un certain point
de son exécution. Lors de sa création par exemple.
Il peut être impossible à annuler.
Les tentatives d'annulation sont ignorées silencieusement.
Un thread peut désactiver son annulation avec la
fonction pthread_setcancelstate.
Le premier argument est PTHREAD_CANCEL_DISABLE
Pour désactiver l'annulation , ou PTHREAD_CANCEL_ENABLE
pour réactiver l'annulation.
Le second argument, s'il n'est pas NULL, pointe vers
une variable qui recevra l'état précédent de l'annulation.
Cet appel, par exemple, désactive l'annulation du
thread appelant.
Gestion de Signaux
Le comportement de l'interaction entre les threads et les signaux varie
d'un type d'UNIX à l'autre. Sous Linux, ce comportement est dicté par
le fait que les threads sont implémentés comme des processus.
Comme chaque thread est un processus distinct, et comme un signal
est délivré à un processus particulier, il n'y a pas d'ambiguïté au
niveau du thread qui va recevoir le signal. Typiquement, les
signaux envoyés de l'extérieur du programme sont envoyés au
processus correspondant au thread principal du programme.
Au sein d'un programme multithread, il est possible pour un thread
d'envoyer un signal à un autre thread bien défini. Utilisez la
fonction pthread_kill pour cela. Son premier paramètre est
l'identifiant du thread et le second, le numéro du signal.
L'appel Système clone
L'appel système clone de Linux est une forme hybride entre fork
et pthread_create , il permet à l'appelant de spécifier les ressources
partagées entre lui et le nouveau processus.
clone nécessite également que vous spécifiiez la région mémoire
utilisée pour la pile d'exécution du nouveau processus.
Bien que nous mentionnions clone pour satisfaire la curiosité du
lecteur, cet appel système ne doit pas être utilisé au sein de
programmes. Utilisez fork pour créer des nouveaux processus et
pthread_create pour créer des nouveaux threads.
Conclusion
Les threads devraient être utilisés pour les programmes
qui ont besoin d'un parallélisme finement contrôlé.
Les processus devraient être utilisés pour des programmes
ayant besoin d'un parallélisme plus grossier.
Le partage de données entre des threads est trivial car
ceux-ci partagent le même espace mémoire. Le partage de
données entre des processus nécessite l'utilisation de
mécanisme IPC (Inter Process Communication).
int pthread_getconcurrency(void);
int pthread_setconcurrency(int new_level);
Comment terminer un processus ?
Merci pour votre attention