Produttore consumatore

Produttore consumatore
  • shape
  • shape


Il problema del produttore-consumatore

Il Problema del produttore-consumatore è un classico problema di sincronizzazione che si affronta nei sistemi concorrenti. Descrive uno scenario in cui dei produttori generano dati e li inseriscono in un buffer condiviso, mentre i consumatori prelevano questi dati per elaborarli.

Immaginiamo una fabbrica dove i produttori producono oggetti e li mettono su una catena di montaggio (il buffer). I consumatori, come operai o robot, prendono gli oggetti dalla catena per lavorarci. Il sistema deve assicurarsi che:

  • I produttori non producano più oggetti di quanti il buffer possa contenere.
  • I consumatori non prelevino oggetti se la catena è vuota.

Il problema del produttore-consumatore si risolve usando meccanismi di sincronizzazione come i semafori per controllare l'accesso al buffer condiviso.

In questa lezione, esploreremo una soluzione usando i semafori, che sono strumenti fondamentali per sincronizzare i thread.

Cos'è un semaforo?

Un semaforo è una struttura dati utilizzata per gestire l'accesso concorrente a una risorsa condivisa. Può essere immaginato come un contatore che tiene traccia di quante risorse sono disponibili in un sistema.

Un semaforo può essere incrementato o decrementato dai thread per indicare il rilascio o l'acquisizione di una risorsa.

Ad esempio, in un buffer con capacità di 5 elementi, un semaforo può contare quanti spazi liberi ci sono (per i produttori) o quanti elementi ci sono (per i consumatori). Quando un thread produttore aggiunge un elemento, il semaforo viene decrementato; quando un thread consumatore rimuove un elemento, il semaforo viene incrementato.

In C#, possiamo utilizzare la classe Semaphore per implementare un semaforo.

La logica del produttore-consumatore

La soluzione al problema prevede l'uso di due semafori:

  • Full: Tiene traccia di quanti elementi ci sono nel buffer, inizializzato a 0 perché il buffer è vuoto all'inizio.
  • Empty: Tiene traccia di quanti spazi vuoti ci sono nel buffer, inizializzato alla capacità massima del buffer.

Inoltre, utilizzeremo un lock (o mutex) per garantire che solo un thread alla volta acceda al buffer condiviso.

La sincronizzazione del produttore e del consumatore avviene coordinando l'uso di semafori e lock.

Implementazione pratica in C#

Ecco un esempio completo che mostra come implementare il problema del produttore-consumatore usando i semafori:

using System;
using System.Threading;

class ProduttoreConsumatore
{
   private const int BufferSize = 5;
   private static int[] buffer = new int[BufferSize];
   private static int inIndex = 0;
   private static int outIndex = 0;

   private static Semaphore empty = new Semaphore(BufferSize, BufferSize); // Spazi vuoti
   private static Semaphore full = new Semaphore(0, BufferSize);          // Elementi presenti
   private static object lockObject = new object(); // Per sincronizzare l'accesso al buffer

   static void Main(string[] args)
   {
       // Creiamo thread produttori e consumatori
       for (int i = 0; i < 3; i++) new Thread(Produttore).Start(i);
       for (int i = 0; i < 2; i++) new Thread(Consumatore).Start(i);
   }

   static void Produttore(object id)
   {
       while (true)
       {
           int dato = new Random().Next(1, 100); // Genera un dato casuale
           empty.WaitOne(); // Aspetta che ci sia spazio libero nel buffer
           lock (lockObject)
           {
               buffer[inIndex] = dato;
               Console.WriteLine($"Produttore {id} ha prodotto: {dato} (Posizione {inIndex})");
               inIndex = (inIndex + 1) % BufferSize;
           }
           full.Release(); // Incrementa il semaforo degli elementi pieni
           Thread.Sleep(new Random().Next(500, 1000)); // Simula il tempo di produzione
       }
   }

   static void Consumatore(object id)
   {
       while (true)
       {
           full.WaitOne(); // Aspetta che ci siano elementi nel buffer
           int dato;
           lock (lockObject)
           {
               dato = buffer[outIndex];
               Console.WriteLine($"Consumatore {id} ha consumato: {dato} (Posizione {outIndex})");
               outIndex = (outIndex + 1) % BufferSize;
           }
           empty.Release(); // Incrementa il semaforo degli spazi vuoti
           Thread.Sleep(new Random().Next(500, 1000)); // Simula il tempo di consumo
       }
   }
}

Come funziona il codice?

Vediamo passo per passo come il codice risolve il problema:

  • Produttore: Genera un dato, aspetta che ci sia spazio disponibile nel buffer (semaforo empty), accede in modo sicuro al buffer (lock) e poi aggiorna l'indice di inserimento (inIndex).
  • Consumatore: Aspetta che ci sia un elemento disponibile nel buffer (semaforo full), preleva un dato in modo sicuro (lock) e aggiorna l'indice di rimozione (outIndex).
  • Sincronizzazione: I semafori full ed empty assicurano che i produttori e i consumatori rispettino le regole di sincronizzazione, mentre il lock garantisce che solo un thread alla volta acceda al buffer.

Paragrafi letti

 

    
0%
       Salva
Esercizi su: 'Produttore consumatore'

 

    Approfondimenti su: 'Produttore consumatore'

     



    Impara l'informatica con noi

    Iscriviti gratis e accedi a tutti i nostri esercizi

    Iscriviti gratis!
    Forum
    Altre materie

    Statistiche

    Nel pannello personale, ogni utente può facilmente tenere traccia di tutti i punti ottenuti negli esercizi. I grafici mostrano in modo chiaro le attività ancora da completare e quanto hai già realizzato!


    Vai alla mia dashboard  

    Resources: To ensure optimal performance and prevent server overload, each user is allocated a limited quota of resources
    ...
    Exercise:
    ...
    ChatGpt
    ...