Avant de commencer

Cette suite de travaux pratique nécessite l’utilisation d’un environnement UNIX. Une machine virtuelle est mise à disposition des étudiants.

Si vous avez un environnement Linux ou Mac OS, vous pouvez vous passer de la machine virtuelle. Veillez dans ce cas à avoir:

  • un environnement de compilation fonctionel

  • un IDE ou un éditeur de texte.

Évaluation

Aucun compte rendu n’est demandé en fin de scéance, l’évaluation sera réalisé sur la dernière scéance de TP (2h).

Les travaux pratiques ainsi que l’évaluation peuvent être réalisés en binôme ou seul à votre convenance.

Librarie pthread

La librairie pthread permet une gestion bas niveau des threads. Elle offre des fonction permettant notamment de créer un thread ou d’attendre la fin un thread.

Pour la compilation un binaire utilisant la librairie pthread il est nécessaire d’ajouter de l’ajouter à l’édition des liens.

1
gcc -o binary_name source_file.c -lpthread

1. Création d’un thread

1.1. Exécution d’une tâche simple

Compilez et exécuter le programme suivant :

thread-creation.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

static void *task_a(void *p_data) {
    printf("Affichage réalisé par le thread exécutant la fonction task_a\n");
    return NULL;
}

int main(void) {
    pthread_t thread;
    pthread_create(&thread, NULL, task_a, NULL);
    printf("Thread créé\n");

    sleep(5);
    printf("Fin du programme principal\n");
    return EXIT_SUCCESS;
}

Question : Expliquez le comportement du programme.

1.2. Execution d’une tâche longue

Compilez et exécuter le programme suivant :

thread-creation-infinite-loop.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

static void *task_a(void *p_data) {
    while (1) {
        printf("Affichage réalisé par le thread exécutant la fonction task_a\n");
        sleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t thread;
    pthread_create(&thread, NULL, task_a, NULL);
    printf("Thread créé\n");

    sleep(5);
    printf("Fin du programme principal\n");
    return EXIT_SUCCESS;
}

Question : Expliquez le comportement du programme.

Question : Expliquez la différence avec un processus lourd (fork)

2. Attente de la fin d’un thread

La méthode pthread_join permet d’attendre la fin d’un thread.

Question : En vous basant sur l’exemple de code de la partie Exécution d’une tâche simple, remplacer l’appel à la fonction sleep(5) par un appel à pthread_join pour attendre la fin du thread.

3. Création de plusieurs threads

Question : Créez un programme appliquant les contraintes suivantes :

  1. Le programme principal créé deux threads et attend la fin de chacun d’entre eux.

  2. Le premier thread :

    • affiche un nombre aléatoire puis attend pendant 2 secondes;

    • répète l’opération précédente 5 fois.

  3. Le second thread :

    • affiche un nombre aléatoire puis attend 2 secondes;

    • répète l’opération précédente 10 fois.

Les algorithmes sont identiques au nombre de répétition près. Une seule fonction doit être utilisée pour réaliser l’exercice.

Pour ce faire utilisez le dernier paramètre de pthread_create permettant de passer un argument au thread.

4. Récupération de la valeur de retour du thread

Question : Reprenez le premier programme Exécution d’une tâche simple et modifier le pour que :

  1. la fonction task_a retourne la valeur 42;

  2. le programme principal récupère la valeur et l’affiche sur la sortie standard.

Un thread peut renvoyer une valeur de retour en utilisant l’instruction return. Dans ce cas, la variable retournée est un pointeur. Veillez à bien initialiser la zone mémoire pointée en utilisant malloc

La récupération de la valeur de retour se fait par le deuxième argument de la fonction pthread_join. Dans ce cas, l’adresse d’un pointeur est envoyé afin que la fonction initialise le pointeur de retour.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void *illegal_handler(void *input) {
    int a = 6;
    return a; // Illegal
}

void *illegal_handler2(void *input) {
    int a = 6;
    return &a; // Illegal
}

void *valid_handler(void *input) {
    int *a = malloc(sizeof(int));
    *a = 6;
    return a;
}

5. Création d’une centaine de threads

Question : Créez un programme appliquant les contraintes suivantes :

  1. Le programme principal créé cent threads et attend la fin de chacun d’entre eux.

  2. Les threads:

    • incrémentent une variable statique indiquant le nombre de threads

    • Récupèrent une valeur aléatoire (vous pouvez utiliser rand() % 5 +1)

    • Écrivent sur la sortie standard "Je suis le thread X et j’attends N secondes"

    • avec X le numéro du thread (variable incrémentée)

    • avec N le nombre aléatoire généré

    • Attendent N secondes (nombre aléatoire généré)

    • Écrivent sur la sortie standard "Le Thread X est terminé"

Utilisez un tableau de 100 éléments pour stocker les threads et pouvoir les attendre.

6. Calcul distribué

Question : Créez un programme appliquant les contraintes suivantes:

  • Le programme principal

    • Créé dix threads et attend la fin de chacun d’entre eux ;

    • Envoi en argument de la création du thread un nombre aléatoire

    • Récupère la valeur de retour de chaque thread et en fait la somme ;

    • Affiche le résultat sur sa sortie standard.

  • Chaque Threads

    • Attend autant de secondes que demandé par la valeur passée en argument ;

    • Retourne le carré de la valeur passé en argument.