Attente active
L’attente active, en génie logiciel, ou polling (parfois appelée aussi « scrutation ») est une technique de programmation que les processus utilisent lorsqu'ils vérifient de façon répétée si une condition est vraie, comme l'attente d'une entrée (clavier ou autre) ou encore la libération d'un verrou.
Cette technique peut également être utilisée pour mettre en attente un programme pour une durée déterminée. Cela était nécessaire sur d'anciens systèmes d'exploitation dont le matériel sous-jacent ne proposait pas de méthode spécifique pour suspendre l'exécution du flot d'instruction pendant une période déterminée. Sur les plateformes modernes proposant des horloges et plusieurs vitesses de processeur, cette forme de suspension du programme est souvent imprécise et signe de programmation naïve.
L'attente active peut, par contre, être une stratégie valide dans certaines circonstances, le plus souvent dans l'implémentation des spinlocks au sein de systèmes d'exploitation conçus pour fonctionner sur des systèmes à processeurs multiples. À part ce genre de cas, les attentes actives devraient être évitées, puisque le processeur pourrait être réattribué à une autre tâche.
Exemple de code en C
Le code en C plus bas montre deux threads qui partagent un entier global i. Le premier thread utilise une attente active pour surveiller un changement de la valeur de i.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
volatile int i; /* i est globale, elle est donc visible par toutes les
fonctions. Elle est également définie comme volatile, car
sa valeur peut être modifiée d'une façon qui n'est pas
prédictible par le compilateur (dans notre cas,
ce sera l'autre thread qui modifiera la valeur). */
/* t1 utilise une attente active pour surveiller la modification
de la valeur de i. */
static void *f1()
{
while (i==0)
{
/* On ne fait rien, à part vérifier la valeur de i */
}
printf("La valeur de i a changé pour %d.\n", i);
return;
}
static void *f2()
{
sleep(60); /* Attente de 60 secondes. */
i = 99;
printf("t2 change la valeur de i à %d.\n", i);
return;
}
int main()
{
int x;
pthread_t t1, t2;
i = 0; /* On fixe la variable globale i à 0. */
x = pthread_create(&t1, NULL, f1, NULL);
if (x != 0)
{
printf("erreur lors de la création de t1.\n");
}
x = pthread_create(&t2, NULL, f2, NULL);
if (x != 0)
{
printf("erreur lors de la création de t2.\n");
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Tous les threads ont terminé.\n");
return 0;
}
Sur un système de type Unix, il est possible de compiler le code ci-dessus de la façon suivante :
$ cc spinlock.c -lpthread
Utilisation du processeur
Dans l'exemple ci-dessus, le second thread (t2) est endormi immédiatement pour 60 secondes. Pendant ce temps, le premier thread vérifie constamment si le second thread a modifié la valeur de i.
Vous pouvez utiliser les outils top
ou uptime
communément installé sur les systèmes de type Unix pour voir comment ce programme utilise le processeur. Lancez le programme comme indiqué :
$ uptime; ./a.out ; uptime 13:25:47 up 53 days, 23:50, 4 users, load average: 0.00, 0.00, 0.00 t2 change la valeur de i à 99. La valeur de i a changé pour 99. Tous les threads ont terminé. 13:26:47 up 53 days, 23:51, 4 users, load average: 0.75, 0.21, 0.07
Bien sûr, chaque système retournera des nombres plus ou moins différents, mais l'important est de remarquer qu'avant de lancer le programme, la charge moyenne du système sur la minute précédente était de 0.00. À la fin de l'exécution de ce programme, la charge moyenne de la dernière minute est montée à 0.75, ce qui est surprenant vu la faible complexité du programme.
Des alternatives à l'attente active
Une alternative est d'utiliser des signaux. La plupart des systèmes d'exploitation et autres bibliothèques de thread permettent d'endormir le premier thread, pour que celui-ci soit réveillé plus tard par un signal envoyé par le second thread lors du changement de la valeur de i. Cette méthode, connue comme programmation sur événement est plus efficace car le thread ne consomme pas de cycle CPU lorsqu'il est endormi.
L'attente active en elle-même peut être grandement améliorée en utilisant une fonction de pause (sleep) trouvable sur la plupart des systèmes d'exploitation. Ainsi le thread sera mis en pause de façon régulière avec un temps d'attente relativement court (une seconde voire moins) pendant lequel il ne gaspillera pas de temps CPU. Si la boucle se contente de tester quelque chose de simple, alors le thread passera la plupart de son temps endormi, et ne gaspillera pas une grande proportion du temps CPU, même si quelques cycles seront toujours consommés.
Cas où l'attente active est appropriée
En programmation bas niveau, dans le cas des pilotes matériels, quelquefois les attentes actives sont nécessaires. Il n'est en effet pas toujours pratique d'inclure un système de signal par interruption pour tous les dispositifs matériels, et en particulier pour ceux qui sont rarement utilisés. Parfois, il est nécessaire d'écrire des informations de contrôle dans un registre du matériel en question, et d'y lire ensuite l'état de ce matériel provoqué par l'écriture précédente. Ces dernières données ne sont parfois pas valides avant quelques cycles, ou dizaines de cycles d'horloges. Le programmeur pourrait utiliser une fonction fournie par le système, mais l'appel à cette fonction prendrait plus de cycles CPU que le temps que le matériel n'en met pour valider les données (en ne considérant pas le cas du changement de thread).
Dans ce genre de cas, il est habituel d'utiliser une attente active qui lit l'état du périphérique jusqu'à ce que celui-ci soit valide. Appeler une fonction du système, quelle qu'elle soit, serait dans ce cas bien moins efficace qu'une attente active de quelques cycles.
Voir aussi
Liens externes (en anglais)
- Description from The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
- Citations from CiteSeer
- Article "User-Level Spin Locks - Threads, Processes & IPC" par Gert Boddaert
- Cours "Introduction to Multiprocessors: Algorithms, Data Structures, and Programming - Spin Locks and Contention" par Maurice Herlihy et Nir Shavit
- Austria SpinLock Class Reference