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.

1. Synchronisation

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.1. Exclusion mutuelle

1.1.1. Création d’un MUTEX

La création d’un MUTEX est réalisé via l’utilisation de la macro PTHREAD_MUTEX_INITIALIZER.

1
2
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Create a mutex

1.1.2. Réservation d’un MUTEX

La réservation d’un MUTEX est réalisé via l’utilisation de la fonction pthread_mutex_lock. Si la resource est déjà réservée, la fonction pthread_mutex_lock retiendra l’éxecution du programme tant que le mutex n’est pas libéré.

1
2
3
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Création
pthread_mutex_lock(&mutex); // Réservation (appel bloquant)

1.1.3. Libération d’un MUTEX

La libération d’un MUTEX est réalisé via l’utilisation de la fonction pthread_mutex_unlock. L’appel à cette fonction permet la libération d’un éventuel thread en attente de la resource (qui serait bloqué par le fonction pthread_mutex_lock). Si la resource n’est pas réservé, l’appel n’a aucun effet.

1
2
3
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Création
pthread_mutex_unlock(&mutex); // Libération de la resource

1.1.4. Utilisation

Compilez et exécuter le programme suivant :

mutex.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>

#define NB_THREADS 5

typedef struct {
    int n;
} data;

void print_char_by_char(char *s, int n) {
    int i = 0;
    while (s[i] != '\0') {

        printf("%c", s[i]);
        fflush(stdout); // Forcing stdout to be displayed
        i++;

        // Sleep for 0.5 second
        struct timespec tim, tim2;
        tim.tv_sec  = 0;
        tim.tv_nsec = 500000000L;
        nanosleep(&tim , &tim2);
    }
    printf("%d \n", n);
    sleep(1);
}

void *mytask(void *p_data) {
    char *message = "Thread_n ";
    data *info = p_data;
    print_char_by_char(message, info->n);
    return NULL;
}

int main(void) {
    printf("main start\n");
    int i;
    pthread_t threads[NB_THREADS];
    data infos[NB_THREADS];
    for (i = 0; i < NB_THREADS; i++) {
        infos[i].n = i;
        pthread_create(&threads[i], NULL, mytask, &infos[i]);
    }

    for (i = 0; i < NB_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("main end\n");
}

Question : Expliquez ce qu’affiche la sortie standard.

Question : En utilisant le système des mutex, modifiez le programme pour que le message s’affiche correctement.

1.2. Semaphores

Contrairement au système de thread utilisé précédemment, le système de gestion des sémaphores présenté dans ces TP n’est pas issue de la librairie pthread. La différence principale se trouve dans la portée des sémaphores :

  • Les sémaphores pthreads ne sont utilisable par les threads d’un programme.

  • Les sémaphores systèmes sont utilisable par plusieurs programmes ayant un lien de parenté ou non (fork).

1.2.1. Création d’un sémaphore

La création d’un sémaphore se fait par l’utilisation de la fonction sem_open.

1
2
3
4
5
6
7
#include <semaphore.h>
sem_t *semaphore = sem_open(
    "mon-semaphore", (1)
    O_CREAT|O_EXCL, (2)
    0777, (3)
    1 (4)
);
1 Nom du sémaphore
2 Options de création (dans notre cas: création s’il n’existe pas et erreur s’il existe)
3 les droits d’accès au sémaphore
4 la valeur initiale du sémaphore s’il est créé
sem_open créé un sémaphore système, s’il n’est pas supprimé par le programme il sera présent lors de la prochaine exécution du programme. L’utilisation de perror permet de récupérer l’erreur. Dans ce cas, la constante SEM_FAILED est retournée.

1.2.2. Réservation d’un sémaphore

La réservation d’un sémaphore s’effectue via l’appel système sem_wait. Il permet de décrémenter le compteur du sémaphore. S’il tombe à zéro alors l’appel est bloquant.

1
2
3
4
#include <semaphore.h>
sem_t *semaphore = sem_open( "mon-semaphore", O_CREAT|O_EXCL, 0777, 1 );
sem_wait(semaphore); // l'appel n'est pas bloquant car le sémaphore est initialisé à 1
sem_wait(semaphore); // l'appel est bloquant car le compteur du sémaphore est 0

1.2.3. Libération d’un sémaphore

La libération d’un sémaphore s’effectue par l’appel système sem_post. Il permet d’incrémenter le compteur du sémaphore et de libérer les processus en attente de la resource.

1
2
3
#include <semaphore.h>
sem_t *semaphore = sem_open( "mon-semaphore", O_CREAT|O_EXCL, 0777, 0);
sem_post(semaphore); // le compteur est incrémenté et est égal à 1

1.2.4. Suppression d’un sémaphore

La suppression d’un sémahore se fait par l’appel système suivant:

1
2
3
4
#include <semaphore.h>
sem_t *semaphore = sem_open( "mon-semaphore", O_CREAT|O_EXCL, 0777, 1 );
sem_close(semaphore); (1)
sem_unlink("mon-semaphore"); (2)
1 Fermeture du sémaphore, le programme ne peut plus l’utiliser
2 Suppression système du sémaphore.

1.2.5. Utilisation

semaphore.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>

#define NB_THREADS 5

typedef struct {
    int n;
} data;

void print_char_by_char(char *s, int n) {
    int i = 0;
    while (s[i] != '\0') {

        printf("%c", s[i]);
        fflush(stdout); // Forcing stdout to be displayed
        i++;

        // Sleep for 0.5 second
        struct timespec tim, tim2;
        tim.tv_sec  = 0;
        tim.tv_nsec = 500000000L;
        nanosleep(&tim , &tim2);
    }
    printf("%d \n", n);
    sleep(1);
}

void *mytask(void *p_data) {
    char *message = "Thread_n ";
    data *info = p_data;
    print_char_by_char(message, info->n);
    return NULL;
}

int main(void) {
    printf("main start\n");
    int i;
    pthread_t threads[NB_THREADS];
    data infos[NB_THREADS];
    for (i = 0; i < NB_THREADS; i++) {
        infos[i].n = i;
        pthread_create(&threads[i], NULL, mytask, &infos[i]);
    }

    for (i = 0; i < NB_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("main end\n");
}

Question : En utilisant le système des sémaphores, modifiez le programme pour que le message s’affiche correctement.

1.2.6. Barrière de synchronisation

Question :

Écrivez un programme qui lance 10 threads et attend la fin de tous les threads.

Chaque thread devra :

  • Exécuter la même fonction

  • Attendre un temps aléatoire (sleep())

  • Attendre la fin des autres threads (à l’aide de sémaphores)

  • Le dernier thread devra réveiller les autres.

  • Vous aurez besoin d’un sémaphore, d’une variable globale et d’un mutex

  • N’hésitez pas a écrire l’algorithme avant de coder