Présentation Création Threads Posix.1c/1003b IEEE Présentation Création
Présentation Un thread est un déroulement d'un programme qui s'exécute parallèlement à d'autres unités en cours d'exécution. Les différents threads d'une application partagent un même espace d'adressage. L'accès concurrentiel aux mêmes données nécessite une synchronisation pour éviter les interférences. En outre chaque thread dispose d'une pile et d'un contexte d'exécution contenant les registres du processeur et un compteur d'instruction. L'idée générale est de détacher le flot d'exécution des ressources
Le contexte processus Contexte du processus Code données et pile Contexte d'exécution Registres généraux Registres d'état SP PC Pile Tas Contexte du noyau Tables fichiers Structure MV Segment données Données Schémas tirés du cours de l’université Carnegie Mellon monté par O’Hallaron et al.. notes de F. Silber-chaussumier Le processus dispose d’un espace d’adressage s’étendant jusqu’à 3 Go. Sur le schéma de cette page, les différents segments sont schématisés en trois zones. La zone “Code” contient le code et les données en écriture seule, la zone “Données” contient les données en lecture/écriture et le “Tas” contient les données allouées dynamiquement. La zone “Pile” représente la pile d’exécution du processus. Le contexte du processus est représenté divisé en deux. Le contexte d’exécution représente les informations sur l’état d’exécution du programme : registres, instruction courante et pointeur de pile. Le contexte du noyau représente les informations sur l’état des ressources nécessaires au processus : structures permettant de gérer la mémoire, les tables de descripteurs pour accéder aux fichiers, aux ports de communication... et le pointeur vers la fin du segment de données, indiqué pointeur brk code
Le contexte processus : Détacher le flot des ressources Code et données Thread Pile Tas Contexte du thread Registres généraux Registres d'état SP PC Données Cette autre vision du processus détache le flot d’exécution des ressources. Les informations nécessaires au cours de déroulement du programme c’est-à-dire la pile et le contexte d’exécution sont isolées des informations concernant les ressources stockant le code, les données et le contexte du noyau. Ceci fait donc apparaître les informations nécessaires au fil d’exécution, en anglais « thread» et les informations qui pourront être partagées par plusieurs fils d’exécution. code Contexte du noyau Tables fichiers Structure MV Segment données
Processus multi-thread Code et données Thread 1 Thread 2 Pile1 Pile2 Contexte du thread Registres généraux Registres d'état SP1 PC1 Contexte du thread Registres généraux Registres d'état SP2 PC2 Tas Données Dans le cas d’un processus multi-thread, les différents fils d’exécution caractérisés par leur pile, l’état des registres et le compteur ordinal se partagent le code, les données, le tas et le contexte du noyau notamment les tables de descripteurs. Les fichiers et les ports de communication par exemple sont partagés. La pile d’un processus habituel n’est contrainte que par la limite de la zone nommée tas, dans laquelle les variables dynamiques sont allouées. En principe, la pile d’un tel processus pourrait croître jusqu’à remplir l’essentiel de l’espace d’adressage du programme, soit environ 3 Go. Dans le cas d’un programme multithread, les différentes piles doivent être positionnées à des emplacements figés dès la création des threads, ce qui impose des limites de taille puisqu’elles ne doivent pas se rejoindre. Dans la bibliothèque POSIX, l’adresse et la taille maximum de la pile sont données par : _POSIX_THREAD_ATTR_STACKADDR et _POSIX_ THREAD_ATTR_STACKSIZE. La taille minimum de la pile est définie par PTHREAD_STACK_MIN correspondant à 16 Ko sous Linux. code Contexte du noyau Tables fichiers Structure MV Segment données
La bibliothèque des threads http://www.linux-kheops.com/doc/man/manfr/man-html-0.9/man3/pthread_create.3.html
Le man Nom pthread_create - créé un nouveau thread Synopsis #include <pthread.h> int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg); Description pthread_create créé un nouveau thread s'éxécutant concurremment avec le thread appelant. Le nouveau thread exécute la fonction start_routine en lui passant arg comme premier argument. Le nouveau thread s'achève soit explicitement en appelant pthread_exit(3) , ou implicitement lorsque la fonction start_routine s'achève. Ce dernier cas est équivalent à appelér pthread_exit(3) avec la valeur renvoyée par start_routine comme code de sortie. L'argument attr indique les attributs du nouveau thread. Voir pthread_attr_init(3) pour une liste complète des attributs. L'argument attr peut être NULL, auquel cas, les attributs par défaut sont utilisés: le thread créé est joignable (non détaché) et utilise la politique d'ordonnancement usuelle (pas temps-réél). Valeur Renvoyée En cas de succès, l'identifiant du nouveau thread est stocké à l'emplacement mémoire pointé par l'argument thread, et 0 est renvoyé. En cas d'erreur, un code d'errur non nul est renvoyé. Erreurs EAGAIN pas assez de ressources système pour créer un processus pour le nouveau thread. il y a déjà plus de PTHREAD_THREADS_MAX threads actifs. Auteur Xavier Leroy <Xavier.Leroy@inria.fr> Traduction Thierry Vignaud <tvignaud@mandrakesoft.com>, 2000 Voir Aussi pthread_exit(3) , pthread_join(3) , pthread_detach(3) , pthread_attr_init(3) .
pthread.h ftp://sources.redhat.com/pub/pthreads-win32/dll-latest/include/ lien
Création d'un thread Un type opaque est utilisé pour distinguer les threads d'une application unsigned long pthread_t (dans la bibliothèque LinuxThreads) Création d'un thread int pthread_create( pthread_t * pThread, const pthread_attr_t * attr, void* (*pFunction) (void*), void * pArg ); Cette routine renvoie 0 si elle a réussi, sinon elle renvoie un numéro de lerreur survenue. pthread_create ne remplit pas la var errno. Définition de types opaques Dans le cadre de l'écriture de programme en plusieurs unités de compilation, il est souvent utile de définir un type de manière opaque, c'est à dire d'en laisser libre l'utilisation sans que l'utilisateur n'ait à connaître sa définition. C'est exactement ce que réalise la bibliothèque standard pour le type FILE : le programmeur sait que fopen() rend une valeur de type FILE * et que cette valeur doit être passée en paramètre des fonctions d'entrées-sorties fprintf(), fputc(), fputs() etc. Il y a beaucoup d'autres exemples de ce type dans la bibliothèque standard. http://www.linux-kheops.com/doc/man/manfr/man-html-0.9/man3/pthread_create.3.html Nom pthread_create - créé un nouveau thread Synopsis #include <pthread.h> int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg); Description pthread_create créé un nouveau thread s'éxécutant concurremment avec le thread appelant. Le nouveau thread exécute la fonction start_routine en lui passant arg comme premier argument. Le nouveau thread s'achève soit explicitement en appelant pthread_exit(3) , ou implicitement lorsque la fonction start_routine s'achève. Ce dernier cas est équivalent à appelér pthread_exit(3) avec la valeur renvoyée par start_routine comme code de sortie. L'argument attr indique les attributs du nouveau thread. Voir pthread_attr_init(3) pour une liste complète des attributs. L'argument attr peut être NULL, auquel cas, les attributs par défaut sont utilisés: le thread créé est joignable (non détaché) et utilise la politique d'ordonnancement usuelle (pas temps-réél). Valeur Renvoyée En cas de succès, l'identifiant du nouveau thread est stocké à l'emplacement mémoire pointé par l'argument thread, et 0 est renvoyé. En cas d'erreur, un code d'errur non nul est renvoyé. Erreurs EAGAIN pas assez de ressources système pour créer un processus pour le nouveau thread. il y a déjà plus de PTHREAD_THREADS_MAX threads actifs. Auteur Xavier Leroy <Xavier.Leroy@inria.fr> Traduction Thierry Vignaud <tvignaud@mandrakesoft.com>, 2000 Voir Aussi pthread_exit(3) , pthread_join(3) , pthread_detach(3) , pthread_attr_init(3) .
Le code C de thread_create /* Thread creation */ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void *), void *arg) { pthread_descr self = thread_self(); struct pthread_request request; if (__pthread_manager_request < 0) { if (pthread_initialize_manager() < 0) return EAGAIN; } request.req_thread = self; request.req_kind = REQ_CREATE; request.req_args.create.attr = attr; request.req_args.create.fn = start_routine; request.req_args.create.arg = arg; sigprocmask(SIG_SETMASK, (const sigset_t *) NULL, &request.req_args.create.mask); __libc_write(__pthread_manager_request, (char *) &request, sizeof(request)); suspend(self); if (self->p_retcode == 0) *thread = (pthread_t) self->p_retval; return self->p_retcode;
Arguments Le premier argument pthread_t * pThread, est un pointeur qui sera réalisé par la routine avec l'identifiant du nouveau thread. le second argument const pthread_attr_t * attr, correspond aux attributs dont on desire doter le nouveau thread. Par défaut, le thread reçoit des attributs standard. Le troisième argument void* (*pFunction) (void*), est un pointeur représentant la fonction principale du nouveau thread (c'est ce qu'il doit faire). Dès la création du thread, la fonction est invoquée en recevant comme argument le pointeur passé en dernière position. Le prototype de cette fonction est imposé et ne peut donc pas être modifié. Le quatrième argument void * pArg est de type void *, il sera ainsi transformé dans le type de l'argument que l'on veur passer au thread. Cet argument est généralement un numéro permettant d'indiquer le travail à réaliser. En 2 l'utilisation de const montre que les attribut ne sont pas modifiés.
Terminaison La terminaison a lieu lorsque La fonction principale se termine. Par l'appel de la fonction pthread_exit le thread est alors éliminé par l'appel de pthread_exit toutes les fonctions de nettoyage sont invoquées
Terminaison Prototype de la fonction void pthread_exit(void * pReturnValue); Attention a ne pas utiliser exit qui stop tout. La fonction “pthread_exit” ne retourne jamais puisque le thread n’existe plus après.
Récupération d'une valeur de retour Pour avoir la valeur de retour d'un thread terminé on utilise int pthread_join( pthread_t thread, void ** pPtrReturnValue ); La fonction peut échouer si le thread attendu n'existe pas ; s'il est détaché ou si le thread demande sa propre fin
Les argument de pthread_join L'argument pthread_t thread, est le thread qui se termine Le deuxième argument void ** pPtrReturnValue est un pointeur sur un void * utilisé pour recevoir la valeur de retour du thread. Cette valeur de retour est spécifiée a la fin du thread grâce a la fonction pthread_exit. Ce dernier argument peut être NULL si nous ne sommes pas intéressé par la valeur de retour du thread.
Récupération d'un entier comme code de retour Pour conserver la portabilité d'un programme, la procédure a lieu en deux étapes dans fonctionthread int i=valretour; pthread_exit((void*)i); dans le main par exemple void * retour; pthread_join(threadid, & retour); Ainsi, il faut utiliser d'abord un pointeur void * temporaire, qu'on transforme ensuite en int.
Interaction avec le thread principal pthread_create() thread Fin pthread_create() pthread_join() ptread_exit() fin pthread_join() exit()
Synchronisation et communication de tâches Dans POSIX, les outils sont plus rapides et plus puissants que les IPC Sémaphore binaire Les mutex sont dédiés à l'exclusion mutuelle. Les attributs des mutex permettent de choisir le protocole de gestion de ressource associé Sémaphores en lecture/écriture POSIX.1c propose les sémaphore en lecture/écriture Aucun protocole de gestion de ressources Variable conditionnelle Utilisée avec un mutex pour créer des moniteurs On peut utiliser une attente bornée qui ne sera jamais signalée. RDV Après la création d'un RDV synchronisé des tâches peuvent s'attendre mutuellement Pour les mutex les attente se font sur FIFO
zones d'exclusions mutuelles La synchronisation est un des enjeux essentiels du développement d'application multithreads entre les différents fils d'exécution concurrents. Pour accéder à des données communes, il est indispensable de mettre en œuvre un mécanisme d'exclusion mutuelle des threads. Cas des mutex Pour créer un mutex, la librairie POSIX fournie la fonction suivante : int pthread_mutex_init( pthread_mutex_t * pMutex, pthread_mutexattr_t * pAttributs ); Pour détruire un mutex, nous utilisons la fonction suivante : int pthread_mutex_destroy(pthread_mutex_t * pMutex);
Paramètres Le premier paramètre est un pointeur sur un pthread_mutex_t destiné a recevoir le descripteur du mutex nouvellement créé. Le second paramètre pAttributs est utilisé pour paramétrer le mutex, cette variable regroupe tous les attributs du mutex, les paramètres par défaut sont obtenus avec un pointeur Null.
Emploi La fonction pthread_mutex_init est employée comme ceci pthread_mutex mutex pthread_mutexattr_t muxeattr /*initialisationdemutexattr*/ /*initialisationdumutex*/ if ((mutex=malloc (sizeof(pthread_mutex_t))==Null) return (-1); pthread_mutex_init(&mutex,&mutexattr);
Fonction de verrouillage et déverrouillage Si le mutex est libre, il peut être immédiatement verrouillé et attribué au thread appelant. Si le mutex est déjà maintenu par un autre thread, la fonction reste bloquée (indéfiniment). Ce n'est pas un point d'annulation. int pthread_mutex_lock(pthread_mutex_t * pMutex); Dès que le thread a fini de travailler avec les données protégées, le mutex doit être déverrouillé pour laisser la place a d’autres thread. Ceci est simplement réalisé en appelant la fonction suivante : int pthread_mutex_unlock(pthread_mutex_t * pMutex);
Attente conditionelle Lorsqu'un processus attend qu'un événement survienne on emploie une technique de synchronisation a base de variable conditionnelle. Un thread attend une condition ; il est avertit par un autre thread lorsqu'elle est réalisée. Lorsqu’un variable de condition n’est plus utilisée, il faut la libérée avec la fonction suivante : int pthread_cond_destroy(pthread_cond_t * pCond);
Attente conditionelle Le principe repose sur deux fonctions de manipulation des conditions int pthread_cond_wait( pthread_cond_t * pCond, pthread_mutex_t * pMutex ); int pthread_cond_signal(pthread_cond_t * pCond); Sans oublier int pthread_cond_init( pthread_condattr_t * pCandAttr
Scénario Toutes les variables de condition ont besoin d’être associé à un mutex spécifique qui va bloquer le thread jusqu'à l’émission de la notification. Thread attendant la condition Thread signalant la condition pthread_mutex_lock pthread_cond_wait déblocage du mutex wait pthread_cond_signal Réveil du thread Tentative de récupérer le mutex pthread_mutex_unlock Fin de pthread_cond_wait
Fonction d'attente temporisée Il faut préciser l'heure maximale et non la durée int pthread_cond_timewait( pthread_cond_t * pCond, pthread_mutex_t * pMutex, struct timespec * expiration ); pthread_cond_wait a le comportement d'un point d'annulation. Lorsqu'un thread reçoit une demande d'annulation durant cette fonction d'attente, elle se termine mais doit récupérer avant le mutex associé à la condition. Cela signifie qu'elle peut bloquer indéfiniment avant de se terminer. struct timespec elle contient un champ contenant le nombre de seconde écoulées depuis 1970 et un champ indiquant le complément en nanosecondes. Pour obtenir la date actuelle time() ou gettimeofday().
Annulation d'un thread L'annulation doit thread doit laisser les données dans un état prévisible, et le seul état prévisible du mutex associé à un appel pthread_cond_wait() est le verrouillage. Bien sur, le thread ne doit pas laisser le mutex bloqué. On utilise une fonction de nettoyage.
Fonction de nettoyage Lorsqu'un thread s'attribue une ressource qui nécessite une libération ultérieure, il enregistre le nom de la routine de libération dans une pile spéciale. Lorsque le thread se termine, les routines sont dépilées et exécutées void pthread_cleanup_push( void(*fonction)(void * argument), void * argument ); void pthread_cleanup_pop(int execution_routine); Rem : ces deux fonctions doivent être dans le meme fonction et dans le même bloc lexical. Un point d’annulation pouvant intervenir à n’importe quel moment et les ressources associées aux threads n’étant pas libérées en fin d’exécution (elles ne le sont qu’à la fin du processus), un mécanisme a été mis en place afin de libérer ses ressources avant qu’il se termine vraiment. Pour une ressource que l’on vient d’allouer à un thread, pthread_cleanup_push permet de préciser la fonction devant être exécutée afin de libérer la ressource, et le paramètre qui sera passé à cette fonction. Ces fonctions sont placées dans une pile spéciale. À la terminaison du programme, les fonctions sont dés-empilées et exécutées. Le programmeur peut lui-même dés-empiler ces fonctions par l’intermédiaire d’un appel à pthread_cleanup_pop. L’unique paramètre précise si la fonction doit être simplement dés-empilée (0) ou aussi exécutée (1). pthread_cleanup_push et pthread_cleanup_pop sont généralement implémentés sous la forme de macros dont la première ouvre un bloc lexical que ferme la seconde.
Ordonnancement Deux niveaux de programmation concurrente sont offert dans Posix les processus et les threads L'ordonnancement peut se faire à deux niveaux local et global
Ordonnancement local P1 P4 P12 Ti Tj Le modèle d'ordonnancement est hiérarchique Les processus sont en concurrence par priorité Le temps alloué à P4 est réparti aux taches Pi
Ordonnancement global 1 2 n T2 P1 P4 P12 T1 Ti Tj Les taches ou les processus sont ordonnancées au même niveau Pi
Ordonnancement Mixte 1 2 n T2 P1 P4 P12 T1 Ti Tj Pi
Avantages/inconvénients Dans un ordonnancement local si une tâche fait une action bloquante le processus est bloqué. Les tâches d'un même processus ne peuvent être ordonnancées sur un multiprocesseurs Pour modifier dans un ordonnancement global un paramètre influençant l'ordonnancement il faut faire un appel système plus lourd qu'un appel de fonction interne au processus. Les tâches d'un même processeurs peuvent être ordonnancées sur plusieurs processeurs
Politique d'ordonnancement SCHED_FIFO La tâche prête (processus) qui a la plus forte priorité au niveau global et qui est arrivée la première SCHED_RR fonctionne comme SCHED_FIFO avec un quantum de temps appliqué par tourniquet SCHED_OTHER défini par l'implémentation SCHED_SPORADIC La politique de base est la politique SCHED_FIFO, avec une quantité de temps alloué à un serveur dont la capacité est cette quantité 32 niveaux de priorité
Les attributs d'une tâche Les attributs d'une tâches peuvent définir Le type d'ordonnancement à appliquer La taille de la pile Une priorité La définition d'un serveur sporadique associé à la tâche L'état attaché ou détaché de la tâche Après la création d'un tâche, il est possible de modifier les attributs d'une tâche. Les attributs d'une tâche ne sont pas portables et ne sont pas définis par la norme POSIX Pour exécuter une tâche de facon synchrone (join) le créateur attendra la fin de la tâche pour poursuivre son exécution. dans le cas contraire (detach state) elle est rendu indépendante.
Les attributs de thread Création et destruction : int pthread_attr_init ( pthread_attr_t * ) int pthread_attr_destroy (pthread_attr_t * ) Lecture : int pthread_attr_getXXX (const pthread_attr_t *, T*) Mise à jour : int pthread_attr_setXXX ( pthread_attr_t *, T ) XXX représente le nom de l'attribut.
Signification et valeur de XXX detachstate : spécifie s’il sera possible de se synchroniser sur le fil d’exécution une fois qu’il sera terminé. PTHREAD_CREATE_JOINABLE pour autoriser la synchronisation PTHREAD_CREATE_DETACHED pour la décliner. schedpolicy correspond à la politique d’ordonnancement employée par le thread. SCHED_OTHER pour l’ordonnancement classique SCHED_RR pour un séquencement temps-réel avec l’algorithme Round Robin SCHED_FIFO pour un ordonnancement temps-réel FIFO. Inheritsched signale si le thread a sa propre configuration d'ordonnancement PTHREAD_EXPLICIT_SCHED ordonnancement spécifique au thread PTHREAD_INHERIT_SCHED ordonnancement hérité
Liste complète int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy); int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy); int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param); int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit); int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit); int pthread_attr_setscope(pthread_attr_t *attr, int scope); int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
Norme POSIX.1003.b
liens http://old.devpaks.org/show.php?devpak=18 http://sources.redhat.com/pthreads-win32/ http://www.humanfactor.com/pthreads/ http://www.llnl.gov/computing/tutorials/pthreads/