Tp n°3 sémaphores correction - systèmes d’exploitation -

Ce document académique présente la correction détaillée du troisième Travail Pratique (TP N°3) du module "Systèmes d'Exploitation Avancés", spécifiquement conçu pour les étudiants de première année ingénieur. Il vise à consolider leur compréhension des mécanismes fondamentaux de synchronisation des threads et processus.

Ce TP couvre les notions essentielles suivantes, illustrées par des implémentations concrètes en langage Java :

  • La conception et la mise en œuvre d'une barrière de synchronisation à l'aide de sémaphores.
  • La résolution du problème classique du Producteur/Consommateur via l'utilisation appropriée des sémaphores.
Tp n°3 sémaphores correction - systèmes d’exploitation -  -

TP N°3 : Correction des Exercices sur la Synchronisation des Threads en Java

Ce document présente la correction des exercices du Travaux Pratiques (TP) n°3, centré sur l'utilisation des sémaphores pour la synchronisation des threads en Java. Il s'inscrit dans le cadre du module "Systèmes d'Exploitation Avancés" pour les étudiants de 1ère année Ingénieur, et a été élaboré par Mme Lilia SFAX durant l'année universitaire 2010-2011.

Barrière de Synchronisation avec Sémaphores

Une barrière de synchronisation est un mécanisme qui assure que tous les threads d'un groupe atteignent un point d'exécution commun avant qu'aucun d'entre eux ne puisse continuer. C'est crucial pour des opérations où plusieurs tâches parallèles doivent se coordonner à des étapes spécifiques.

Classe Processus

Cette classe modélise un thread qui simule une activité avant d'arriver à un point de rendez-vous. Chaque instance attend de manière aléatoire avant d'invoquer la méthode proc_rdv() de l'objet RendezVous partagé.

package rdv;

class Processus extends Thread {
    private RendezVous rv;

    public Processus(RendezVous rv) {
        this.rv = rv;
    }

    public void run() {
        try {
            Thread.sleep(6000 * (int) Math.random());
            rv.proc_rdv();
        } catch (InterruptedException e) {
            // Gestion de l'interruption du thread
        }
    }
}

Classe Main

La classe Main est le point d'entrée du programme. Elle instancie un objet RendezVous avec le nombre total de processus attendus et crée ensuite ce nombre de threads Processus, les démarrant pour qu'ils participent à la barrière.

package rdv;

public class Main {
    public static void main(String argv[]) {
        int N = Integer.parseInt(argv[0]); // Nombre de processus
        RendezVous rv = new RendezVous(N);
        Processus process[] = new Processus[N];

        for (int i = 0; i < N; i++) {
            process[i] = new Processus(rv);
            process[i].setName("Processus" + i);
            process[i].start();
        }
    }
}

Classe RendezVous

Cette classe implémente la logique de la barrière. Elle utilise un sémaphore s pour bloquer les processus arrivés en avance et un sémaphore mutex pour garantir l'accès exclusif à la variable nbArrives. Le dernier processus à arriver libère tous les autres threads bloqués.

package rdv;

import java.util.concurrent.Semaphore;

public class RendezVous {
    private int nbArrives;
    private int N;
    private Semaphore s; // Sémaphore pour bloquer les threads
    private Semaphore mutex; // Sémaphore pour protéger l'accès à nbArrives

    public RendezVous(int MaxProcess) {
        nbArrives = 0;
        s = new Semaphore(0, true); // Initialisé à 0, donc tout acquire bloque au début
        mutex = new Semaphore(1, true); // Mutex pour garantir l'accès exclusif
        N = MaxProcess;
    }

    public void proc_rdv() throws InterruptedException {
        mutex.acquire(); // Acquiérir le mutex avant de modifier nbArrives
        nbArrives = nbArrives + 1;

        if (nbArrives < N) {
            mutex.release(); // Libérer le mutex avant de se bloquer
            System.out.println(Thread.currentThread().getName() + " se bloque ");
            s.acquire(); // Se bloquer en attendant les autres processus
        } else {
            // Le dernier processus est arrivé
            mutex.release(); // Libérer le mutex
            System.out.println(Thread.currentThread().getName()
                    + " est le dernier, il reveille les autres" + " processus ("+nbArrives+" sont arrivés)");
            for (int i = 1; i <= N - 1; i++) // Réveiller N-1 processus bloqués
                s.release(); // Libérer un permis pour chaque processus en attente
        }
    }
}

Problème du Producteur/Consommateur

Le problème Producteur/Consommateur est un défi classique de la programmation concurrente où des threads (producteurs) génèrent des données et d'autres threads (consommateurs) les traitent, en utilisant une ressource partagée comme un tampon. Les sémaphores sont employés pour prévenir la production sur un tampon plein et la consommation depuis un tampon vide, et pour assurer l'accès mutuellement exclusif au tampon.

Classe Item

Cette classe représente les objets ou données qui seront produits par les producteurs et consommés par les consommateurs. Elle est simple et contient une valeur de type double.

package prodConsVect;

public class Item {
    public double item;

    public Item(double item) {
        this.item = item;
    }
}

Classe Main

La classe Main configure l'environnement du problème Producteur/Consommateur. Elle initialise un Vector comme tampon partagé et trois sémaphores : empty (places disponibles), full (éléments présents) et mutex (accès au tampon).

package prodConsVect;

import java.util.Vector;
import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) {
        Vector<Item> v = new Vector<Item>(); // Tampon partagé
        Semaphore empty = new Semaphore(10); // Sémaphore pour les places vides (initialement 10)
        Semaphore full = new Semaphore(0); // Sémaphore pour les éléments pleins (initialement 0)
        Semaphore mutex = new Semaphore(1); // Sémaphore pour l'accès mutuellement exclusif au tampon

        Consommateur cons = new Consommateur(v, full, empty, mutex);
        Producteur prod = new Producteur(v, full, empty, mutex);

        cons.start(); // Démarrer le consommateur
        prod.start(); // Démarrer le producteur
    }
}

Classe Producteur

Le thread producteur est responsable de la création d'objets Item et de leur ajout au tampon partagé. Il utilise les sémaphores pour s'assurer qu'il y a de la place disponible avant d'ajouter un élément et que l'accès au tampon est exclusif pendant l'opération.

package prodConsVect;

import java.util.Vector;
import java.util.concurrent.Semaphore;

public class Producteur extends Thread {
    private Vector<Item> v;
    private Semaphore full;
    private Semaphore empty;
    private Semaphore mutex;

    public Producteur(Vector<Item> v, Semaphore full, Semaphore empty,
                      Semaphore mutex) {
        super();
        this.v = v;
        this.full = full;
        this.empty = empty;
        this.mutex = mutex;
    }

    public void run() {
        while (true) {
            try {
                empty.acquire(); // Demander une place vide (attend si le tampon est plein)
                mutex.acquire(); // Acquiérir le mutex pour accéder au tampon
                Item i = new Item(Math.random());
                v.add(i); // Ajouter l'élément au tampon
                System.out.println("Produced : " + i.item);
                mutex.release(); // Libérer le mutex
                full.release(); // Signaler qu'un élément est disponible
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Classe Consommateur

Le thread consommateur retire les objets Item du tampon partagé pour les traiter. Il utilise les sémaphores pour s'assurer qu'il y a des éléments à consommer avant d'en prendre un et que l'accès au tampon est exclusif pendant cette opération.

package prodConsVect;

import java.util.Vector;
import java.util.concurrent.Semaphore;

public class Consommateur extends Thread {
    private Vector<Item> v;
    private Semaphore full;
    private Semaphore empty;
    private Semaphore mutex;

    public Consommateur(Vector<Item> v, Semaphore full, Semaphore empty,
                       Semaphore mutex) {
        super();
        this.v = v;
        this.full = full;
        this.empty = empty;
        this.mutex = mutex;
    }

    public void run() {
        while (true) {
            try {
                full.acquire(); // Demander un élément disponible (attend si le tampon est vide)
                mutex.acquire(); // Acquiérir le mutex pour accéder au tampon
                Item i = v.firstElement(); // Récupérer le premier élément
                v.remove(0); // Supprimer l'élément du tampon
                System.out.println("Consumed : " + i.item);
                mutex.release(); // Libérer le mutex
                empty.release(); // Signaler qu'une place est devenue vide
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Foire Aux Questions (FAQ)

Qu'est-ce qu'une barrière de synchronisation ?
Une barrière de synchronisation est un point de rendez-vous dans un programme concurrent où plusieurs threads doivent se rassembler et attendre que tous les autres threads désignés les rejoignent. Une fois que tous les threads ont atteint la barrière, ils peuvent tous continuer leur exécution simultanément.
Comment les sémaphores sont-ils utilisés pour résoudre le problème Producteur/Consommateur ?
Pour le problème Producteur/Consommateur, les sémaphores gèrent la disponibilité des ressources et l'accès exclusif. Un sémaphore empty contrôle les espaces disponibles dans le tampon (le producteur l'acquiert avant d'écrire). Un sémaphore full compte les éléments remplis (le consommateur l'acquiert avant de lire). Un sémaphore binaire mutex assure qu'un seul thread accède au tampon partagé à la fois, évitant ainsi les corruptions de données.
Quel est le rôle du sémaphore mutex dans ces exemples ?
Le sémaphore mutex, initialisé à 1, agit comme un verrou d'accès exclusif. Il garantit qu'une seule thread à la fois peut exécuter une section critique du code, comme la modification d'une variable partagée (nbArrives dans la barrière) ou l'accès à une structure de données partagée (le Vector dans le producteur/consommateur). Cela prévient les conditions de concurrence et assure l'intégrité des données.

Cela peut vous intéresser :

Partagez vos remarques, questions , propositions d'amélioration ou d'autres cours à ajouter dans notre site

Enregistrer un commentaire (0)
Plus récente Plus ancienne