Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Programmation système
  3. Les threads
Extrait - Programmation système Maîtrisez les appels système Linux avec le langage C (Nouvelle édition)
Extraits du livre
Programmation système Maîtrisez les appels système Linux avec le langage C (Nouvelle édition) Revenir à la page d'achat du livre

Les threads

Principes des threads

Un thread (en anglais, thread signifie fil), parfois traduit par unité d’exécution, est une portion de code qui peut s’exécuter à l’intérieur d’un processus. Un processus peut contenir différents threads, qui s’exécutent indépendamment les uns des autres, éventuellement simultanément si le système est multiprocesseur ou multicœur. Comme les threads s’exécutent dans le contexte d’un processus, ils partagent le même espace mémoire et disposent des mêmes ressources (fichiers ouverts, tubes, sockets, objets mémoire partagée, etc.). Pendant qu’un thread est en attente d’une ressource ou du retour d’un appel système (entrée/sortie longue par exemple), un autre thread du même processus peut s’exécuter.

Un processus traditionnel est un cas particulier, il n’exécute qu’un seul thread.

Le principal intérêt des threads est qu’ils partagent directement toutes les ressources du processus, et en particulier l’espace mémoire. La communication entre les threads est donc automatique, sans nécessiter de mécanisme particulier.

La principale difficulté de la programmation multithread réside dans la synchronisation des différents threads d’un processus, pour éviter...

Threads et processus

Quand un processus est créé, il est d’abord monothread. Ce thread initial, appelé thread principal (main thread) car c’est lui qui exécute la fonction main(), peut utiliser un appel système pour créer d’autres threads au sein du processus.

Il n’y a pas de hiérarchie particulière entre les threads d’un processus ni d’ordre particulier d’exécution.

Cependant, comme le thread principal exécute la fonction main(), quand il exécute l’instruction return ou termine la fonction main(), il provoque la terminaison du processus et de tous ses threads.

1. Threads et appels système de niveau processus

Nous avons vu qu’au départ Unix a été conçu comme un système multiprocessus, mais monothread. Par conséquent, de nombreux appels système s’appliquent au niveau du processus lui-même, quel que soit son nombre de threads.

a. Appel système execve() en contexte multithreads

Cet appel système provoque la terminaison immédiate de tous les threads du processus, à l’exception de celui qui a effectué l’appel.

b. Appel système fork() en contexte multithreads

Cet appel système provoque, dans le processus enfant, la disparition de tous les threads du processus parent, à l’exception de celui qui a effectué...

Gestion des threads

Un thread (hormis le thread principal, créé automatiquement par le noyau) est créé par un appel système dans lequel on lui associe une fonction à exécuter.

Quand le thread se termine, soit parce que sa fonction se termine ou exécute l’instruction return, soit parce qu’il exécute un appel système spécifique, soit parce qu’il a été l’objet d’un appel système d’annulation, il reste dans un état proche de celui d’un processus zombie, jusqu’à ce qu’un thread du même processus le "joigne" (sauf s’il avait préalablement été "détaché").

Nous allons étudier les différents appels système correspondant à ces phases de déroulement d’un thread, puis nous verrons les techniques de synchronisation des threads.

1. Création d’un thread : pthread_create()

Pour créer un thread, Linux utilise l’appel système clone() qui permet de créer différents types de processus enfants. Dans le cas d’un thread, cet appel système est utilisé avec une option spécifique, CLONE_THREAD.

Dans la suite, nous décrivons les fonctions de gestion de threads, de la bibliothèque libpthread, car il est conseillé de les utiliser de préférence aux appels système sous-jacents : elles sont normalisées POSIX et portables.

a. pthread_create()

syntaxe

#include <pthread.h> 
int pthread_create(pthread_t *pthread_id, const pthread_attr_t *attr, 
void *(*start_routine) (void *), void *arg); 

Arguments

pthread_id

Adresse où retourner l’identifiant du thread

attr

Adresse d’une structure d’attributs du thread, ou NULL

start_routine

Adresse de la fonction à exécuter par le thread

arg

Pointeur à passer à la fonction en argument, ou NULL

Valeur retournée

-1

Erreur, code erreur positionné dans la variable errno

0

Succès

Description

Cet appel système crée un nouveau thread dans le processus. Ce thread exécute la fonction pointée par start_routine, en lui passant comme argument le pointeur arg.

L’identifiant du thread est retourné dans la variable pointée...

Synchronisation des threads

Comme les threads d’un processus partagent le même espace mémoire et la plupart des ressources du processus, il est délicat de gérer les éventuels conflits d’accès. Il faut donc utiliser des outils de synchronisation entre les threads, pour pouvoir garantir un accès exclusif aux éléments communs.

Il est possible de gérer les accès concurrents avec les sémaphores, étudiés dans les chapitres précédents, mais leur mise en œuvre est lourde et consommatrice de ressources. Il est généralement préférable d’utiliser des méthodes spécifiquement conçues pour les threads, en particulier les mutex et les variables conditionnelles.

1. Mutex

Un mutex (mutual exclusion) est un type particulier de sémaphore, implémenté sous forme d’une variable de type pthread_mutex_t, qui peut prendre uniquement deux états, libre ou verrouillé.

Un thread peut acquérir ou libérer un mutex. À un instant donné, un seul thread possède le mutex. Seul le thread qui possède le mutex peut le libérer.

Le mutex est utilisable par tous les threads ayant accès à la zone mémoire dans laquelle il a été créé.

Un mutex permet donc de "protéger" des ressources communes (le plus souvent des variables globales) en déterminant quel thread peut y avoir accès. Cette protection est de nature coopérative, car un thread qui ne tient pas compte du mutex peut tout à fait accéder à la ressource commune alors que le mutex est verrouillé.

a. Initialisation d’un mutex

Un mutex est une variable de type pthread_mutex_t. Elle est le plus souvent définie en tant que variable globale statique, de façon à être accessible par tous les threads du processus.

Il est possible de créer un mutex pendant l’exécution du processus, dans une zone mémoire allouée dynamiquement, mais son initialisation doit être gérée par une fonction spécifique.

Cette variable doit être initialisée pour pouvoir être utilisée ultérieurement. Généralement, elle est initialisée dès sa définition...