¿Como has llegado a este portal?
Trabajo
Curiosidad
Amigos/as
Recomendación
No recuerdo
Compartir con alguien

Blog ASP.NET


El modelo productor consumidor
1. Introducción
En una aplicación desarrollada he tenido que investigar el modelo Productor / Consumidor. 

Este modelo consiste en uno o varios productores que general información y uno o varios consumidores que consume esta información.
Para poder llevar a cabo la función utilizan un repositorio común donde el productor "consume y guarda" y el consumidor "produce y retira". Este repositorio puede ser una cola de datos, una carpeta de ficheros, un array en memoria, etc.

2. Elementos
Las características que tienen que tener estos procesos son:
  • Independencia de funcionamiento: El productor y el consumidor generan y/o consumen recursos disponibles en forma independiente, incluso sin conocer el estado del otro componente. Con esto se consigue que el productor produce recusos a su ritmo, almacenando en el repositorio mientras el consumidor recoge los datos cuando los haya, permaneciendo inactivo cuando no los haya.
  • Control de bloqueo: El repositorio común debe bloquearse cuando es utilizado por un productor o consumidor. Los consumidores para evitar consumir un elemento varias veces y los productores para disponer en exclusiva del repositorio para generar los elementos.

    3. Codificación
    Utilizo una aplicación de consola para generar un productor y un consumidor. El productor general números aleatorios (como todos los ejemplos que manejan este modelo y para faciliar el tutorial no voy a hacer una excepción).

    Los pasos a seguir para crearlo son:
    a) - Crear una clase contenedora.
    b) - Crear una clase productor.
    c) - Crear una clase consumidor.
    d) - Iniciar el proceso.

    4. Paso a paso

    a) - Crear una clase contenedora
    Para este modelo creo los repositorios en memoria con dos objetos de Tipo ArrayList.
    Plegar

        1  class Repositorio

        2     {

        3         public ArrayList listarecolectora = new ArrayList();

        4         public ArrayList listaescritora = new ArrayList();

        5         public int PausaProductor = 5;

        6         public int PausaConsumidor = 30;

        7     }



    b) - Crear una clase productor
    Esta clase tiene los métodos GenerarValores() y DetenerProceso(). La variable FinalizarProceso es una bandera para cancelar la producción.
    Plegar

        1     /// <summary>

        2     /// Clase Productor

        3     /// </summary>

        4     class Productor

        5     {

        6         #region Variables

        7 

        8         Random random = new System.Random();

        9         Repositorio repositorio;

       10         public bool FinalizarProceso = false;

       11         //Maximo número aleatorio generador por el productor

       12         const int MaximoNumeroGenerado = 100; 

       13 

       14         int iteracion;

       15         public int Iteracion

       16         {

       17             get { return iteracion; }

       18             set { iteracion = value; }

       19         }

       20 

       21         #endregion

       22 

       23         #region Constructor

       24 

       25         public Productor(Repositorio repositorio)

       26         {

       27             this.repositorio = repositorio;

       28             iteracion = 0;

       29         }

       30 

       31         #endregion

       32 

       33         #region Metodos

       34 

       35         public void GenerarValores()

       36         {

       37             while (!FinalizarProceso)

       38             {

       39                 //Bloque lista y produzo elementos con una pausa de ValorPausa

       40                 lock (repositorio.listaescritora)

       41                 {

       42                     iteracion++;

       43                     int valor = random.Next(MaximoNumeroGenerado);

       44                     Console.WriteLine(String.Format("<br>Iteración: {0} - Produzco valor: {1}", iteracion, valor));

       45                     repositorio.listaescritora.Add(valor);

       46                 }

       47                 Thread.Sleep(this.repositorio.PausaProductor);

       48             }

       49         }

       50 

       51         public void DetenerProceso()

       52         {

       53             FinalizarProceso = true;

       54         }

       55 

       56         #endregion

       57 

       58     }



    c) - Crear una clase consumidor
    Esta clase tiene los métodos ConsumirValor() y DetenerProceso(). La variable FinalizarProceso es una bandera para cancelar la consumición.
    Plegar

        1     /// <summary>

        2     /// Clase Consumidor

        3     /// </summary>

        4     class Consumidor

        5     {

        6         #region Variables

        7 

        8         Repositorio repositorio;

        9         String Nombre;

       10 

       11         public bool EnProceso = true;

       12         public bool FinalizarProceso = false;

       13 

       14         int iteracion;

       15         public int Iteracion

       16         {

       17             get {return iteracion;}

       18             set {iteracion = value;}

       19         }

       20 

       21         #endregion

       22 

       23         #region Constructor

       24 

       25         public Consumidor(Repositorio repositorio, String Nombre)

       26         {

       27             this.repositorio = repositorio;

       28             this.Nombre = Nombre;

       29             iteracion = 0;

       30         }

       31 

       32         #endregion

       33 

       34         #region Metodos

       35 

       36         public void ConsumirValor()

       37         {

       38             while (EnProceso)

       39             {

       40                 Thread.Sleep(this.repositorio.PausaConsumidor);

       41                 lock (repositorio.listaescritora)

       42                 {

       43                     if (repositorio.listaescritora.Count > 0)

       44                     {

       45                         //Consumo primero elemento de la lista y desbloque recurso

       46                         iteracion++;

       47                         Console.WriteLine(String.Format("{0} consume valor: {1} - Iteración: {2}", Nombre, repositorio.listaescritora[0], iteracion));

       48                         repositorio.listarecolectora.Add(repositorio.listaescritora[0]);

       49                         repositorio.listaescritora.RemoveAt(0);

       50                     }

       51 

       52                     if (FinalizarProceso)

       53                     {

       54                         if (repositorio.listaescritora.Count == 0) EnProceso = false;

       55                     }

       56                 }

       57             }

       58         }

       59 

       60         public void DetenerProceso()

       61         {

       62             FinalizarProceso = true;

       63         }

       64 

       65         #endregion

       66 

       67 

       68 

       69     }


    d) Código principal
    Creo un productor y dos consumidores.
    Cada objeto se crea en un hilo independiente. Primero comienza a trabajar los consumidores y luego el productor.
    En la constante Pausa establezco el tiempo durante el cual el productor genera valores.
    Pasado este lapso detengo el productor y los consumidores con el método genérico DetenerProceso().
    La variable PausaProductor y PausaConsumidor miden el tiempo de pausa que utilizan para generar o producir un nuevo recurso siempre que éste se encuentre disponible en el repositorio.
    Plegar    

        1     static class Inicio
        2
         {

        3         //Tiempo utilizado para que los consumidores consuman los elementos restantes

        4         const int Pausa = 300;

        5 

        6 

        7         static void Main()

        8         {

        9             Comienzo();

       10         }

       11 

       12         public static void Comienzo()

       13         {

       14 

       15             Repositorio repositorio = new Repositorio();

       16 

       17             Productor productor = new Productor(repositorio);

       18 

       19             ThreadStart threadstartproductor = new ThreadStart(productor.GenerarValores);

       20             Thread threadproductor = new Thread(threadstartproductor);

       21 

       22             Consumidor consumidor_1 = new Consumidor(repositorio, "Consumidor_1");

       23             Consumidor consumidor_2 = new Consumidor(repositorio, "Consumidor_2");

       24 

       25             ThreadStart threadstartconsumidor_1 = new ThreadStart(consumidor_1.ConsumirValor);

       26             Thread threadconsumidor_1 = new Thread(threadstartconsumidor_1);

       27 

       28             ThreadStart threadstartconsumidor_2 = new ThreadStart(consumidor_2.ConsumirValor);

       29             Thread threadconsumidor_2 = new Thread(threadstartconsumidor_2);

       30 

       31             threadconsumidor_1.Start();

       32             threadconsumidor_2.Start();

       33 

       34             threadproductor.Start();

       35 

       36             Thread.Sleep(Pausa);

       37 

       38             productor.DetenerProceso();

       39             Console.WriteLine("<br>El productor recibe señal de terminar el proceso");

       40 

       41             consumidor_1.DetenerProceso();

       42             Console.WriteLine("<br>El consumidor_1 recibe señal de terminar el procesos");

       43 

       44             consumidor_2.DetenerProceso();

       45             Console.WriteLine("<br>El consumidor_2 recibe señal de terminar el procesos");

       46 

       47             Console.WriteLine(String.Format("<br>Elementos pendientes en repositorio: {0}", repositorio.listaescritora.Count));

       48 

       49             Console.WriteLine(String.Format("<br>Elementos consumidos Consumidor_1: {0} - Consumidor_2: {1}", consumidor_1.Iteracion, consumidor_2.Iteracion));

       50             Console.WriteLine(String.Format("<br>Total elementos producidos: {0}- Total elementos consumidos: {1}", productor.Iteracion, repositorio.listarecolectora.Count));

       51 

       52         }

       53     }



    5. Resultado
    Realizo tres pruebas con el siguiente objetivo:
  • Igual velocidad de producción y consumidores
  • El productor es más veloz que los consumidores
  • El productor es más lento que los consumidores
    5.1 - Primer prueba:
    La velocidad del productor y los consumidores es la misma.
    En general se consumen todos los valores inmediatamente después de su generación.

    Plegar
    Iteración: 1 - Produzco valor: 62
    Consumidor_1 consume valor: 62 - Iteración: 1
    Iteración: 2 - Produzco valor: 12
    Iteración: 3 - Produzco valor: 49
    Consumidor_1 consume valor: 12 - Iteración: 2
    Consumidor_2 consume valor: 49 - Iteración: 1
    Iteración: 4 - Produzco valor: 35
    Iteración: 5 - Produzco valor: 32
    Consumidor_1 consume valor: 35 - Iteración: 3
    Consumidor_2 consume valor: 32 - Iteración: 2
    Iteración: 6 - Produzco valor: 76
    Iteración: 7 - Produzco valor: 38
    Consumidor_1 consume valor: 76 - Iteración: 4
    Consumidor_2 consume valor: 38 - Iteración: 3
    Iteración: 8 - Produzco valor: 13
    Consumidor_2 consume valor: 13 - Iteración: 4
    Iteración: 9 - Produzco valor: 34
    Consumidor_1 consume valor: 34 - Iteración: 5
    Iteración: 10 - Produzco valor: 18
    Iteración: 11 - Produzco valor: 84
    Consumidor_1 consume valor: 18 - Iteración: 6
    Consumidor_2 consume valor: 84 - Iteración: 5
    Iteración: 12 - Produzco valor: 6
    Iteración: 13 - Produzco valor: 0
    Consumidor_1 consume valor: 6 - Iteración: 7
    Consumidor_2 consume valor: 0 - Iteración: 6
    Iteración: 14 - Produzco valor: 56
    Iteración: 15 - Produzco valor: 32
    Consumidor_2 consume valor: 56 - Iteración: 7
    El productor recibe señal de terminar el proceso
    El consumidor_1 recibe señal de terminar el proceso
    El consumidor_2 recibe señal de terminar el proceso
    Elementos pendientes en repositorio: 1
    Elementos consumidos Consumidor_1: 8 - Consumido
    Total elementos producidos: 15- Total elementos
    Consumidor_1 consume valor: 32 - Iteración: 8


    5.2 - Segunda prueba:
    El productor es más veloz que los consumidores.
    ValorPausa del productor: 5
    ValorPausa de los consumidores: 30
    El productor genera valores con mayor rapidez que los consumidores. Ha quedado dos elementos sin consumirse.
    Plegar
    Iteración: 1 - Produzco valor: 42
    Consumidor_1 consume valor: 42 - Iteración: 1
    Iteración: 2 - Produzco valor: 96
    Consumidor_1 consume valor: 96 - Iteración: 2
    Iteración: 3 - Produzco valor: 56
    Consumidor_2 consume valor: 56 - Iteración: 1
    Iteración: 4 - Produzco valor: 60
    Iteración: 5 - Produzco valor: 85
    Consumidor_1 consume valor: 60 - Iteración: 3
    Consumidor_2 consume valor: 85 - Iteración: 2
    Iteración: 6 - Produzco valor: 68
    Iteración: 7 - Produzco valor: 69
    Consumidor_1 consume valor: 68 - Iteración: 4
    Consumidor_2 consume valor: 69 - Iteración: 3
    Iteración: 8 - Produzco valor: 27
    Consumidor_1 consume valor: 27 - Iteración: 5
    Iteración: 9 - Produzco valor: 85
    Iteración: 10 - Produzco valor: 69
    Iteración: 11 - Produzco valor: 61
    Consumidor_2 consume valor: 85 - Iteración: 4
    Consumidor_1 consume valor: 69 - Iteración: 6
    Iteración: 12 - Produzco valor: 91
    Iteración: 13 - Produzco valor: 23
    Consumidor_1 consume valor: 61 - Iteración: 7
    Consumidor_2 consume valor: 91 - Iteración: 5
    Iteración: 14 - Produzco valor: 96
    Consumidor_1 consume valor: 23 - Iteración: 8
    Iteración: 15 - Produzco valor: 21
    Consumidor_2 consume valor: 96 - Iteración: 6
    Iteración: 16 - Produzco valor: 33
    El productor recibe señal de terminar el proceso
    El consumidor_1 recibe señal de terminar el procesos
    El consumidor_2 recibe señal de terminar el procesos
    Elementos pendientes en repositorio: 2
    Elementos consumidos Consumidor_1: 8 - Consumidor_2: 6
    Total elementos producidos: 16- Total elementos consumidos: 14


    5.3 - Tercera prueba:
    El productor es más lento que los consumidores.
    ValorPausa del productor: 30
    ValorPausa de los consumidores: 5
    Plegar
    Iteración: 1 - Produzco valor: 71
    Consumidor_1 consume valor: 71 - Iteración: 1
    Iteración: 2 - Produzco valor: 89
    Consumidor_2 consume valor: 89 - Iteración: 1
    Iteración: 3 - Produzco valor: 41
    Consumidor_2 consume valor: 41 - Iteración: 2
    Iteración: 4 - Produzco valor: 57
    Consumidor_2 consume valor: 57 - Iteración: 3
    Iteración: 5 - Produzco valor: 86
    Consumidor_2 consume valor: 86 - Iteración: 4
    Iteración: 6 - Produzco valor: 1
    Consumidor_2 consume valor: 1 - Iteración: 5
    Iteración: 7 - Produzco valor: 43
    Consumidor_2 consume valor: 43 - Iteración: 6
    Iteración: 8 - Produzco valor: 61
    Consumidor_2 consume valor: 61 - Iteración: 7
    Iteración: 9 - Produzco valor: 64
    El productor recibe señal de terminar el proceso
    El consumidor_1 recibe señal de terminar el procesos
    El consumidor_2 recibe señal de terminar el procesos
    Elementos pendientes en repositorio: 1
    Elementos consumidos Consumidor_1: 1 - Consumidor_2: 7
    Total elementos producidos: 9- Total elementos consumidos: 8


    6. Links
    Una mejora interesante al modelo Productor / Consumidor utilizando dos colas como repositorio
    Double Queue

  •