Un petit exemple “hello world” Un véritable exemple : mise en place

Transcription

Un petit exemple “hello world” Un véritable exemple : mise en place
L3 Informatique (& Miage TP optionnel)
Programmation système et Réseau
Université Nice – Sophia Antipolis
F. BAUDE
Sujet réalisé par O. Dalle et F. Baude
Année universitaire 2007/2008
Séance de TP N°11
11.pthread – Utilisation des threads POSIX
Il s’agit pour ce TP de prendre contact avec une librairie de threads conforme la norme
POSIX pthread. Dans chaque exercice, vous pourrez tester les différences liées aux systèmes
d’exploitation sous-jacents (comme par exemple la taille du buffer d’E/S) en compilant puis
exécutant chaque programme sur les différentes architectures disponibles :
• Sur un PC Linux quelconque pour le système Linux
• Sur la machine thalie pour le système Solaris
• Sur la machine uranie pour le système OSF/1
Le Makefile fourni permet de compiler un même programme sur différents systèmes en
s’adaptant aux spécificités de celui-ci (fichier d’inclusion dalle-thread.h) et surtout en
produisant chaque fois un fichier binaire différent.
Etudiez bien ce Makefile, vous y découvrirez quelques astuces souvent fort utiles.
Créez votre répertoire de TP ~/SR/11phtread et placez-vous dessus. Récupérez dans votre
répertoire de TP le fichier Fourniture11.tgz sur le site de cet enseignement et déballez-le.
Dans chacun des 2 sous-répertoires, vous remarquerez que vous n’avez pas de Makefile.gen,
mais directement un Makefile prêt à l’emploi..
Un petit exemple “hello world”
Regardez les fichiers hello.c (version non multi-thread qui affiche “hello world”) puis hellothread.c (version multithread du même programme) situés dans le répertoire Ex1.
Regardez attentivement les fonctions de création de threads.
Constatez également qu’il faut
(1) que la thread hello ait fait son travail avant que la thread world puisse faire le sien;
(2) que les 2 threads aient fait leur travail, avant que la thread main écrive le retour
chariot.
Dans hello-thread2.c, nous avons forcé la thread qui exécute la fonction hello à rendre la main
avant d’avoir fait son travail (appel à notre macro YIELD() qui si elle le peut, appelle la
fonction sched_yield(2)). Mais, grâce au join dans la thread world, la thread world n’affichera
pas son message, tant que la thread hello n’aura pas fait de même et terminé.
Pour vous en convaincre, testez les deux exécutables produits à partir de ce fichier source, ces
2 exécutables se nommant : hello-thread2 et hello-thread2-nojoin (dans lequel l’instruction
pthread_join est désactivée), et regardez l’effet produit.
Un véritable exemple : mise en place d’un filtre
Placez-vous maintenant dans le répertoire Ex2
Le programme filter doit transformer en majuscules les caractères lus sur l’entrée standard et
afficher le résultat sur la sortie standard. Ce travail est réalisé par plusieurs threads, suivant le
modèle producteur/consommateur. Le producteur (thread reader) lit l’entrée standard par
blocs, ou buffers, de taille BUFSIZ. Chaque bloc est stocké par la thread reader dans un des
NBUF buffers (la zone tampon entre le producteur et le consommateur est donc de taille
NBUF). Le consommateur (thread filter writer) prend un buffer rempli par le producteur, en
modifie le contenu, et enfin écrit le résultat sur la sortie standard.
Compiler puis lancer filter sur un petit fichier (par exemple, sur filter.c).
Notez que dans les fonctions alloc_buffer et enable_buffer se cachent toutes les
synchronisations entre les 2 threads. En effet, pour qu’un buffer puisse être traité puis vidé, il
faut qu’il soit rempli; pour qu’un buffer puisse être rempli, il faut qu’il soit vide !
L3 Informatique (& Miage TP optionnel)
Programmation système et Réseau
Université Nice – Sophia Antipolis
F. BAUDE
Sujet réalisé par O. Dalle et F. Baude
Année universitaire 2007/2008
Première étape : rajouter une troisième thread
Quand vous aurez bien compris les contraintes de synchronisation entre les 2 threads,
rajoutez une thread filter qui décharge la thread “writer” de la partie filtrage des données.
Pour que ces 3 threads se coordonnent correctement, rajoutez un état ST MODIFIED et
transformez les conditions d’attente des threads en conséquence.
Expliquez au passage le commentaire de la fonction enable_thread /* dans version originale :
cond_signal */ (on vous rappelle que la version originale ne mettait en œuvre que 2 threads).
Seconde étape : rajouter une troisième thread
A présent, rajouter un fprintf(stderr,...) suivi de fflush(stderr) dans chacune des 3 threads entre
les fonctions alloc et enable, de la forme suivante : Je suis la thread ... et je travaille pour
l’itération x.
Changez aussi le nombre de buffer pour 50 au lieu de 4 (variable NBUF). Puis testez cette
modification sur différents systèmes (Linux, OSF, Solaris).
Attention au fait suivant : l’ordre d’exécution peut être différent entre la première exécution
du programme sur un fichier donné et les exécutions suivantes sur ce même fichier car cela
dépend des accès au disque, via le cache. Justement, rappelons quel est le rôle du cache disque
• en écriture vers le disque : terminer rapidement l’´ecriture en allant déposer les
données en mémoire plutôt que sur le disque
• en lecture depuis le disque : de profiter de données qui auraient déjà été chargées
depuis le disque et qui se trouvent dans le cache.
Premier essai Utilisez filter une première fois sur un gros fichier non encore dans le cache, et
dont la lecture peut s’avérer longue. Recherchez un gros fichier accessible en lecture, par
exemple l’aide d’une commande find telle que la suivante :
find / -type f -size +2000k -perm +111 -print 2> /dev/null
Lancez plusieurs fois l’exécution du programme sur ce gros fichier, en veillant à rediriger la
sortie d’erreur dans un autre fichier (redirection 2>) pour pouvoir étudier attentivement ce qui
se passe au début de l’exécution. Vous pourrez peut-être constater que la thread reader ne
remplit pas forcément toutes les cases du tampon avant que les autres aient la main. Sur un
très
gros
fichier
comme
celui-ci
par
exemple,
/u/profs/buffa/Desktop/setup.app/Contents/Resources/Java/Installer.zip
on
observe
également des comportements non uniformes quant à l’ordonnancement des threads filter et
writer.
Quel est l’ordonnancement entre les threads ?
Maintenant, essayons de qualifier l’ordonnancement de threads mis en œuvre par la librairie
de threads. Voici une liste de questions à vous poser :
• Est-ce que la thread filter (qui ne fait aucune E/S) a perdu la main au milieu d’un travail ?
• Comment se fait-il que parfois une thread arrive à enchainer jusqu’`a NBUF itérations avant
de perdre la main ? Et d’ailleurs, pourquoi perd-elle la main ?
Avec vos réponses, vous devriez être convaincu que l’ordonnancement entre les threads est
tel qu’une thread garde la main tant qu’elle peut s’exécuter. On n’est donc pas en présence
d’un ordonnancement à temps partagé. Pour savoir si l’ordonnancement est éventuellement
préemptif, il nous faudrait pouvoir allouer des priorités différentes aux 3 threads … cela sort
du cadre de ce TP.