Laboratoire 13 - Outils de synchronisation
Sommaire
Remarque: n’oubiez pas, lors de l’utilisation d’appels systèmes, de traiter les cas d’erreur.
Producteurs/consommateurs
- Le programme
produc_conso
avec desint
comme compteurs.
#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);
}
// La déclaration des deux compteurs permettant de savoir s'il y a une donnée disponible pour le consommateur et si le producteur doit produire une donnée
int empty;
int full;
#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++) {
// Gestion de la disponibilité des données
while (empty <= 0) {
attendre();
}
empty--;
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", 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();
// Gestion de la disponibilité des données
full++;
}
}
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++) {
// Gestion de la disponibilité des données
while (full <= 0) {
attendre();
}
full--;
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\"\n", id_consommateur, liste[j]);
// Gestion de la disponibilité des données
empty++;
}
}
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
// 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;
srand(time(NULL)); // Initialise le générateur de nombres aléatoires
// Initialisez les compteurs
full = 0;
empty = TAILLE_MAX;
// Création de l'ensemble des threads, qui exécuteront la fonction producteur
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 ) ;
}
// Affiche les valeurs finales de full et empty.
// On s'attend à 0 et 10 respectivement
printf("Full: %d, Empty: %d\n", full, empty);
// Libérer la mémoire
free(arg_conso);
free(arg_pro);
free(cons_thread);
free(prod_thread);
return 0;
}
- Le programme
produc_conso
avec des mutex pour les index de lecture et d’écriture.
#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);
}
// La déclaration des deux compteurs permettant de savoir s'il y a une donnée disponible pour le consommateur et si le producteur doit produire une donnée
int empty;
int full;
// Les mutex pour éviter les accès concurrent à l'index d'écriture et lecture.
pthread_mutex_t mutex_prod = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_cons = PTHREAD_MUTEX_INITIALIZER;
#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++) {
// Gestion de la disponibilité des données
while (empty <= 0) {
attendre();
}
empty--;
pthread_mutex_lock(&mutex_prod);
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", 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();
// Gestion de la disponibilité des données
pthread_mutex_unlock(&mutex_prod);
full++;
}
}
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++) {
// Gestion de la disponibilité des données
while (full <= 0) {
attendre();
}
full--;
pthread_mutex_lock(&mutex_cons);
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\"\n", id_consommateur, liste[j]);
// Gestion de la disponibilité des données
pthread_mutex_unlock(&mutex_cons);
empty++;
}
}
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
// Initialisez les compteurs
full = 0;
empty = TAILLE_MAX;
// 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 ) ;
}
// Affiche les valeurs finales de full et empty.
// On s'attend à 0 et 10 respectivement
printf("Full: %d, Empty: %d\n", full, empty);
// Libérer la mémoire
free(arg_conso);
free(arg_pro);
free(cons_thread);
free(prod_thread);
return 0;
}
- Le programme
produc_conso
complété avec deux sémaphores.
#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);
}
// La déclaration des deux sémaphores permettant de savoir s'il y a une donnée disponible pour le consommateur et si le producteur doit produire une donnée
sem_t empty;
sem_t full;
// Les mutex pour éviter les accès concurrent à l'index d'écriture et lecture.
pthread_mutex_t mutex_prod = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_cons = PTHREAD_MUTEX_INITIALIZER;
#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++) {
// Gestion de la sémaphore
sem_wait(&empty);
pthread_mutex_lock(&mutex_prod);
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", 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();
// Gestion de la sémaphore
pthread_mutex_unlock(&mutex_prod);
sem_post(&full);
}
}
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++) {
// Gestion de la sémaphore
sem_wait(&full);
pthread_mutex_lock(&mutex_cons);
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\"\n", id_consommateur, liste[j]);
// Gestion de la sémaphore
pthread_mutex_unlock(&mutex_cons);
sem_post(&empty);
}
}
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
// Initialisez les sémaphores
sem_init(&full, 0, 0);
sem_init(&empty, 0, TAILLE_MAX);
// 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 producteur
for (i=0; i!= nombre_produ; i++){
arg_pro[i] = i;
pthread_create( prod_thread + i, NULL , producteur, &arg_pro[i]) ;
}
// Création de l'ensemble des threads, qui exécuteront la fonction consommateur
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);
// Affiche les valeurs finales de full et empty.
// On s'attend à 0 et 10 respectivement
int f, e;
sem_getvalue(&full, &f);
sem_getvalue(&empty, &e);
printf("Full: %d, Empty: %d\n", f, e);
// Ajoutez la destruction des sémaphores
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}
- Version avec mutex:
- La valeur du compteur de disponibilité des ressources peut être incorrecte à cause d’un problème de concurrence dans sa modification. Ceci peut amener une donnée à être consommée en double.
- Version avec compteurs:
- Mêmes problèmes que la version avec mutex
- La valeur du compteur de disponibilité des consommateurs peut être incorrecte à cause d’un problème de concurrence dans sa modification.
- Certaines données peuvent être consommées en double à cause d’un problème de concurrence lors de la modification et de l’accès à l’index d’écriture.
- Certaines données produites peuvent être écrasées par d’autres producteurs en raison d’un problème de concurrence lors de la modification et de l’accès à l’index d’écriture.
- Le programme peut rester bloqué si les 2 problèmes précédents empêchent un consommateur de consommer le nombre de ressources attendues.