Laboratoire 13 - Outils de synchronisation (Suite)

Sommaire

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;
}
  1. 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 fonction attendre) puis retenter.

  2. 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.

  3. Finalement, complétez votre programme produc_conso en remplaçant les deux compteurs globaux par des sémaphores

  4. 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.