Laboratoire 13 - Outils de synchronisation (Suite)
Producteurs/consommateurs
Dans le problème du producteur et consommateur, il y a un ensemble de producteurs qui ont pour travail de générer une donnée et de la mettre en attente dans une file d’attente globale. Le travail du consommateur est de récupérer cette donnée et de la consommer.
- L’objectif du laboratoire est de développer plusieurs versions du programme
produc_conso
en vous basant sur le squelette de code ci-dessous. Ce programme prend en argument un nombre de producteurs et un nombre de consommateurs, chacun représenté par un thread. - Nous utiliseron des sémaphores afin de gérer la création/consommation des données. Vous aurez besoin des méthodes
sem_wait
sem_post
sem_init
sem_destroy
.man sem_overview
pour plus d’informations.
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define MAXDONNEE 5 // Nombre maximal de données produites par chaque producteur
void attendre() {
int sleep_time = (rand() % 5) + 1;
// int sleep_time = 1;
sleep(sleep_time);
}
// À compléter: Ajoutez la déclaration des deux sémaphores permettant de savoir si le consommateur a une donnée disponible et si le producteur doit produire une donnée
// À compléter: Ajoutez des mutex pour éviter les accès concurrent aux index d'écriture et de lecture.
#define TAILLE_MAX 10
// Liste d'attente
char liste[TAILLE_MAX][100];
int index_ecriture=0;
int index_lecture=0;
//Nombre de consommateurs/producteurs
int nombre_produ, nombre_conso;
int TOTALDONNEE;
void* producteur(void *id) {
int id_producteur = *((int *) id);
for (int i = 0; i < MAXDONNEE; i++) {
// À compléter: Gestion de la sémaphore et du mutex
int j = index_ecriture;
index_ecriture++;
if (index_ecriture == TAILLE_MAX)
index_ecriture = 0;
// Génération de la donnée
sprintf (liste[j], "Le producteur %d a créé la donnée %d\n", id_producteur, i);
printf("(+) Le producteur %d a produit : \n\tLe producteur %d a créé la donnée %d\n",id_producteur ,id_producteur, i);
attendre();
// À compléter: Gestion de la sémaphore et du mutex
}
}
void* consommateur(void *id) {
int id_consommateur = *((int *) id);
// Calcul du nombre de données à consommer pour chaque consommateur
int nb_a_consommer = TOTALDONNEE / nombre_conso + (TOTALDONNEE % nombre_conso > id_consommateur);
for (int i = 0; i < nb_a_consommer; i++) {
// À compléter: Gestion de la sémaphore
int j = index_lecture;
index_lecture++;
if (index_lecture == TAILLE_MAX)
index_lecture = 0;
printf ("(-) Consommateur %d a consommé la donnée : \n\t\"%s\"", id_consommateur, liste[j]);
// À compléter: Gestion de la sémaphore
}
}
int main(int argc, char** argv) {
int i;
if(argc == 3){
char* endptr = NULL;
nombre_produ = strtol(argv[1], &endptr, 0);
if (*endptr != '\0' || nombre_produ <= 0) {
fprintf(stderr, "Le nombre de producteurs doit être un nombre entier supérieur à 0\n");
return 1;
}
endptr = NULL;
nombre_conso = strtol(argv[2], &endptr, 0);
if (*endptr != '\0' || nombre_conso <= 0) {
fprintf(stderr, "Le nombre de consommateurs doit être un nombre entier supérieur à 0\n");
return 1;
}
}else{
printf("usage : ./produc_conso nombre_producteurs nombre_consommateurs\n");
return 1;
}
srand(time(NULL)); // Initialise le générateur de nombres aléatoires
// À compléter: Initialisez les sémaphores
// Création et la gestion des threads
pthread_t *prod_thread=NULL;
pthread_t *cons_thread=NULL;
prod_thread = malloc(sizeof(pthread_t) * (nombre_produ));
if(prod_thread==NULL){
perror("malloc prod_thread");
return 1;
}
cons_thread = malloc(sizeof(pthread_t) * (nombre_conso));
if(cons_thread==NULL){
perror("malloc cons_thread");
free(prod_thread);
return 1;
}
int *arg_pro=NULL, *arg_conso=NULL;
arg_pro = malloc(nombre_produ*sizeof(int));
if(arg_pro==NULL){
perror("malloc arg_pro");
free(cons_thread);
free(prod_thread);
return 1;
}
arg_conso = malloc(nombre_conso*sizeof(int));
if(arg_conso==NULL){
perror("malloc arg_conso");
free(arg_pro);
free(cons_thread);
free(prod_thread);
return 1;
}
TOTALDONNEE = nombre_produ * MAXDONNEE;
// Création de l'ensemble des threads, qui exécuteront la fonction philosophe
for (i=0; i!= nombre_produ; i++){
arg_pro[i] = i;
pthread_create( prod_thread + i, NULL , producteur, &arg_pro[i]) ;
}
for (i=0; i!= nombre_conso; i++){
arg_conso[i] = i;
pthread_create( cons_thread + i, NULL , consommateur, &arg_conso[i]) ;
}
// Attendre que l'ensemble des threads soit terminé.
for (i=0; i!= nombre_produ; i++){
pthread_join( prod_thread[i], NULL ) ;
}
for (i=0; i!= nombre_conso; i++){
pthread_join( cons_thread[i], NULL ) ;
}
// Libérer la mémoire
free(arg_conso);
free(arg_pro);
free(cons_thread);
free(prod_thread);
// Ajoutez la destruction des sémaphores
return 0;
}
-
Modifiez le squelette de
produc_conso
afin d’utiliser deux entiers (int
) comme compteurs globaux. Un pour indiquer s’il ya des donneés disponibles pour le consommateur et un autre pour indiquer si le producteur doit produire une donnée. Lorsqu’un producteur ne peut pas produire ou qu’il n’y a pas de donnée disponible pour un consommateur, celui-ci doit attendre entre 1 et 5 secondes (utilisez la fonctionattendre
) puis retenter. -
Modifiez votre programme créé à l’étape précédente en y ajoutant un mutex pour gérer l’accès à l’index d’écriture et un mutex pour gérer l’accès à l’index de lecture.
-
Finalement, complétez votre programme
produc_conso
en remplaçant les deux compteurs globaux par des sémaphores -
Observez le comportement de chacune des version du programme avec différents arguments.
- 1 producteur et 1 consommateur
- 1 producteur et plusieurs consommateurs
- Plusieurs producteurs et 1 consommateur
- Plusieurs producteurs et plusieurs consommateurs Tentez d’expliquer les problèmes observés.
- Note: Les problèmes de synchronisation sont sensibles aux timing et parfois difficile à reproduire. Nous vous recommandons de décommenter la deuxième ligne de la fonction
attendre
afin que le temps d’attendre soit constant. Il est aussi utile de répéter chacune des exécutions à quelques reprises avant de conclure qu’il n’y a pas de problème. - Indice: Regardez les valeurs de vos compteurs/sémaphores et la correspondance entre les données produites et consommées pour trouver les problèmes.