#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NB_THREADS 3
void *travailUtile(void *null)
{
int i;
double resultat=0.0;
for (i=0; i<1000000; i++)
{
resultat = resultat + (double)random();
}
printf("resultat = %e\n",resultat);
pthread_exit((void *) 0);
}
int main (int argc, char *argv[])
{
pthread_t thread[NB_THREADS];
pthread_attr_t attr;
int rc, t;
void *status;
/* Initialisation et activation d’attributs */
pthread_attr_init(&attr); //valeur par défaut
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); //attente du thread possible
for(t=0; t<NB_THREADS; t++)
{
printf("Creation du thread %d\n", t);
rc = pthread_create(&thread[t], &attr, travailUtile, NULL);
if (rc)
{
printf("ERROR; le code de retour de pthread_create() est %d\n", rc);
exit(-1);
}
}
/* liberation des attributs et attente de la terminaison des threads */
pthread_attr_destroy(&attr);
for(t=0; t<NB_THREADS; t++)
{
rc = pthread_join(thread[t], &status);
if (rc)
{
printf("ERROR; le code de retour du pthread_join() est %d\n", rc);
exit(-1);
}
printf("le join a fini avec le thread %d et a donne le status= %ld\n",t, (long)status);
}
pthread_exit(NULL);
}
Ce programme crée 3 threads qui font des choses très utiles et attend leur terminaison.
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define VECTOR_LENGTH 400
#define NB_THREADS 4
int vec1[VECTOR_LENGTH]; //Vector 1
int vec2[VECTOR_LENGTH]; //Vector 2
int res;
void *computeDotProduct(void * n)
{
//Compute the dot product of 100 elements of vec1 and vec2
//Update the variable res --> Need of synchronisation here
}
int main (int argc, char *argv[])
{
//Initialise vec1 and vec2
for(t=0;t<VECTOR_LENGTH;t++){
vec1[i] = ...
vec2[i] = ...
}
//Create 4 threads to compute the result
//Remove threads
}
Refaire l’exercice précédent de la section « Besoin de synchronisation » en utilisant les files de messages.
Les E/S asynchrones présentent l’avantage de pouvoir initier une requête d’E/S (par exemple une lecture d’un fichier sur disque) et de faire autre chose en attendant la complétion de cette dernière.
La structure de base qu’utilisent les API aio (asynchronous Input/Output) est la structure aiocb. Elle caractérise l’entrée /sortie à effectuer et contient un certain nombre de champs dont (man aio):
aio_nbytes
: le nombre d’octets à lire ou écrire par l’E/S asynchrone,
aio_filedes
: le fichier sur lequel l’E/S doit être effectuée,
aio_buf
: le tampon devant contenir les données lues ou les données à écrire,
aio_offset
: le déplacement à partir du début du fichier.
Voici un exemple montrant une utilisation basique d’une E/S asynchrone :
#include <aio.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
if(argc != 2)
{
printf("Usage: %s {filename}\n", argv[0]);
return -1;
}
struct aiocb cb; // bloc de contrôle de l’E/S asynchrone
struct aiocb * cbs[1];
//Ouverture du fichier (spécifié en argument) sur lequel l’E/S va être effectuée
FILE * file = fopen(argv[1], "r+");
//definition du bloc de contrôle de l’entrée/sortie
cb.aio_buf = malloc(11);
cb.aio_fildes = fileno(file); //récupérer le descripteur d’un fichier à partir de son nom
cb.aio_nbytes = 10;
cb.aio_offset = 0;
//lancer la lecture
aio_read(&cb);
//Suspension du processus dans l’attente de la terminaison de la lecture
cbs[0] = &cb;
aio_suspend(cbs, 1, NULL);
printf("operation AIO a retourne %d\n", aio_return(&cb));
return 0;
}
Il est évident que ce programme ne fait rien d’autre qu’implémenter une lecture synchrone avec les API des E/S asynchrones. L’idéal serait de lancer la lecture et de continuer à faire autre chose et être notifié de la fin de l’opération d’E/S par l’OS.
Pour qu’un signal soit envoyé au processus effectuant une E/S asynchrone lorsque celle-ci est terminée,
on utilise le champ aio_sigevent
de la structure aiocb,
cette structure définie la notification de la complétion de l’E/S demandée. Elle est constituée des champs suivants :
sigev_notify
: le mécanisme utilisé pour la notification. Pour les programmes mono thread la valeur doit être à SIGEV_SIGNAL (sinon SIGEV_THREAD).
sigev_signo
: le numéro de signal à envoyer. Il existe un ensemble de signaux (temps réels)
utilisables par l’application qui se situent entre les macros SIGTERMIN et SIGTERMAX qui se trouvent définies dans signal.h.
sigev_value
: union d’un entier et d’un pointeur délivré au handler du message
struct aiocb my_aiocb
/* AIO Signal configuration */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
my_aiocb.aio_sigevent.sigev_signo = SIGIO;
my_aiocb.aio_sigevent.sigev_value.sival_int = 0;
Pour utiliser ce type de mécanisme, il faut définir dans la structure de contrôle de l’E/S le signal (entre SIGRTMIN et SIGRTMAX) qui va nous notifier la complétion de cette dernière et définir une fonction à exécuter à la réception de ce signal avec la primitive sigaction(). Dans sigev_value, on peut mettre une information à envoyer avec le signal pour, par exemple, différencier les E/S dont on reçoit le signal de complétion.
/* Set up the signal handler */
struct sigaction sig_act;
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_RESTART|SA_SIGINFO;
/* Function to call */
sig_act.sa_sigaction = aio_completion_handler;
/* Link the AIO request with the Signal Handler, sigev_signo is the chosen sigev_signo*/
sigaction(SIGIO, &sig_act, NULL);
Faites un programme (mono thread) qui lance 2 lectures et une écriture sur des fichiers (différents ou pas) et qui affiche ce qui a été lu pour les lectures et un acquittement pour l’écriture.
Reprenez l’exercice du produit scalaire, mais le scénario sera, cette fois-ci, plus réaliste. En effet, on suppose que les vecteurs de données se trouvent dans des fichiers différents.