Ejemplo Sencillo del Uso de Threads

Simulacion de un Lavadero de Autos

Fecha: de publicación (08-02-2005)
Autor: Marcos Donalisio ([email protected])

 


El Programa se basa en una Simulacion de un Lavadero de Autos. Esta Compuesto por una Clase Clientes (Productor/Consumidor): la cual se encarga de poner un Auto en el Lavadero, Una Clase EmpleadoLavaAutos (Productor/Consumidor): Que se encarga se Sacar un Auto del Lavadero y Una vez Lavado lo Vuelve a poner en el Lavadero para que el Cliente lo Retire. Una Clase Lavadero (Monitor): donde se encuentran los Autos a Lavar y Lavados (BufferAutos) y Una Clase Vehiculo que representa al Auto que el Cliente deja en el Lavadero

Hice Un Ejemplo Sencillo Para poder mostrar como es el uso de Productor/Consumidor Mediante Threads, Para Que se Bloquea La Memoria, Como Es La Comunicacion entre Hilos, y Ademas para que lo Puedan entender Aquellos que recien se Introducen en el Mundo de Los Threads.

Bueno Como bien dije el Programa cuenta principalmente con dos Threads y un Monitor, Ambos Threads con Productores y Consumidores, A Que me refiero con esto, el Cliente Tiene Un Auto que Lleva al Lavadero (Productor) y Que Luego lo Va a Buscar una Vez que este Limpio (Consumidor). El EmpleadoLavaAuto Saca un Auto Para Lavarlo (Consumidor) Y Lo vuelve a Poner Limpio en el Buffer de Autos (Productor). Se Preguntaran a que me Refiero con Buffer. El Buffer Vendria a ser un Arreglo (Array) del Tipo Vehiculo ya que lo que vamos a poner ahi van a ser Autos y no otra Cosa. Veanlo como si fuera el Playon del Lavadero. Ademas Cuenta con otra clase tambien importante que se llama Lavadero (Monitor), es muy importante esta Clase ya que en ella se realizan todas las operacion de Sacar un Auto para Lavar, Poner un Auto Lavado, son cosas que tienen que ver con el Buffer y sobre el control del Mismo que mas adelante Explicaremos. Bueno Empecemos.

 

Clase Vehiculo

Public Class Vehiculo

   Private Shared Codigo As Integer
   Private Nombre As String
   Private Marca As String
   Private Modelo As String
   Private MyCodigoVehiculo As Integer
   Private Estado As Integer '1 si esta Sucio, 2 si lo estan Lavando, 3 si esta lavado

   Sub New(ByVal Nombre As String, ByVal Marca As String, ByVal Modelo As String)
      Me.Codigo += 1 'Le Sumo 1 a la Variable Estatica
      'Al ser una variable estatica a medida que se Van Creando los Objetos
      'Este toma el Valor que quedo en la variable y le suma 1
      'Si no Fuese estatica todos los Objetos Creados tendrian el Mismo Codigo = 1
      Me.MyCodigoVehiculo = Me.Codigo 'Le Asigno el Codigo al Objeto para Guardarlo
      Me.Marca = Marca  'Le Asigno el Valor del Parametro
      Me.Modelo = Modelo 'Le Asigno el Valor del Parametro
      Me.Nombre = Nombre 'Le Asigno el Valor del Parametro
      Me.Estado = 1
   End Sub

   Public Function getCodigoVehiculo() As Integer
      Return Me.MyCodigoVehiculo
   End Function

   Public Function getEstadoVehiculo() As Integer
      Return Me.Estado
   End Function

   Public Function setEstadoVehiculo(ByVal Estado As Integer)
      Me.Estado = Estado
   End Function
End Class

Esta Clase Cuenta con un atributo statico (Shared) Llamado Codigo el Cual en Cada Instancia de esa clase le va sumando 1 y Luego se lo asigno al atributo MyCodigoVehiculo para que cada objeto tenga su propio codigo, Ademas cuenta con atributos tales como Nombre, Marca, Modelo, y el Estado que va a indicar si el Auto esta Sucio (1), si se esta Lavando (2), o si ya esta Limpio o Lavado (3).

El Constructor recibe por parametros el Nombre, La Marca y el Modelo. ya que el Codigo de cada vehiculo se genera a medida que crean los objetos, y el Estado se inicializa en 1

Cuenta con tres metodos, getCodigoVehiculo() el Cual devuelve un Entero, osea el Codigo del vehiculo. el metodo getEstadoVehiculo() que devuelve tambien un Entero el cual retorna el Estado del Mismo y el metodo setEstadoVehiculo(Estado) el cual recibe un parametro que es un Entero asignandole un nuevo estado para controlar si esta lavando o si ya esta Lavado.

 

Clase Cliente (Thread)

Public Class Cliente

   Private L As Lavadero
   Private MyAuto As Vehiculo

   Sub New(ByVal L As Lavadero)
      Me.L = L 'le Asigno el Monitor, Ya que a los Procesos los Controlo Ahi
   End Sub


   Public Sub Run()
      MyAuto = New Vehiculo("Peugeot", "206", "2005")
      L.PonerAutoALavar(MyAuto)
      MyAuto = L.SacarMyAutoLavado(MyAuto.getCodigoVehiculo)
   End Sub

End Class


La Clase Cuenta con los Atributos L del Tipo Lavadero (Monitor) y MyAuto del Tipo Vehiculo.
Tiene Un Contructor que recibe por parametro el Monitor lo Cual L toma la Referencia para que el Cliente Pueda Llamar los Metodos del Monitor.

Tiene Un Metodo Run() que vendria a ser el Metodo a la cual le voy a pasar como referencia cuando cree los Hilos, y asi lo pueda ejecutar.

Clase EmpleadoLavaAuto (Thread)

 

Public Class EmpleadoLavaAuto
   Private L As Lavadero
   Private myAuto As Vehiculo

   Sub New(ByVal L As Lavadero)
      Me.L = L
   End Sub

   Public Sub Run()
      While L.HayAutoParaLavar()
         Me.myAuto = L.SacarAutoALavar()
         L.PonerAutoLavado(Me.myAuto)
      End While
   End Sub
End Class


Esta Clase si se dan Cuenta es igual que la del Cliente. Cuenta con los Mismo atributos y Metodos, con la diferencia de que en este Thread, dentro del Metodo Run() hay un While, debido a que el lavadero tiene un solo empleado que lava los autos, en el contructor tambien tomo la referencia del Monitor (Lavadero) para que este pueda Llamar a los Metodos del Mismo.

 

Clase Lavadero (Monitor)

Imports System.Windows.Forms
Imports System.Threading

Public Class Lavadero


   Private BufferAutos(25) As Vehiculo
   Private mon As Monitor
   Private Lista As ListBox
   'Declaro un Buffer de Vehiculos En donde van estar los vehiculos
   'este Va A Contener 25 Vehiculos

   Sub New(ByVal myLista As ListBox)
      Me.Lista = New ListBox
      Me.Lista = myLista
   End Sub

   Public Function PonerAutoALavar(ByVal MyAuto As Vehiculo)
      SyncLock BufferAutos
         While EstaLlenoBufferAuto()
            mon.Wait(BufferAutos)
         End While
         Dim i As Integer
         For i = 0 To BufferAutos.Length - 1
            If BufferAutos(i) Is Nothing Then
               Exit For
            End If
         Next
         BufferAutos(i) = MyAuto
         Me.Lista.Items.Add("Cliente Puso Un Auto a Lavar. Codigo: " & MyAuto.getCodigoVehiculo & " Estado: " & MyAuto.getEstadoVehiculo)
         mon.PulseAll(BufferAutos)
      End SyncLock
   End Function

   Public Function SacarAutoALavar() As Vehiculo
      SyncLock BufferAutos
         While EstaVacioBufferAutos() Or Not HayAutoParaLavar()
            mon.Wait(BufferAutos)
         End While
         Dim i As Integer
         For i = 0 To BufferAutos.Length - 1
            If Not BufferAutos(i) Is Nothing Then
               If BufferAutos(i).getEstadoVehiculo = 1 Then
                  Exit For
               End If
            End If
         Next
         BufferAutos(i).setEstadoVehiculo(2)
         Me.Lista.Items.Add("Empleado Saca Un Auto A Lavar. Codigo: " & BufferAutos(i).getCodigoVehiculo & " Estado: " & BufferAutos(i).getEstadoVehiculo)
         mon.PulseAll(BufferAutos)
         Return BufferAutos(i)
      End SyncLock
   End Function

   Public Function PonerAutoLavado(ByVal MyAuto As Vehiculo)
      SyncLock BufferAutos
         While Not EstoyLavandoUnAuto()
            mon.Wait(BufferAutos)
         End While
         Dim i As Integer
         For i = 0 To BufferAutos.Length - 1
            If Not BufferAutos(i) Is Nothing Then
               If BufferAutos(i).getCodigoVehiculo = MyAuto.getCodigoVehiculo Then
                  Exit For
               End If
            End If
         Next
         BufferAutos(i).setEstadoVehiculo(3) 'Pongo el Vehiculo Limpio
         Me.Lista.Items.Add("Empleado Pone Un Auto Lavado. Codigo: " & BufferAutos(i).getCodigoVehiculo & " Estado: " & BufferAutos(i).getEstadoVehiculo)
         mon.PulseAll(BufferAutos)
      End SyncLock
   End Function

   Public Function SacarMyAutoLavado(ByVal CodigoVehiculo As Integer) As Vehiculo
      SyncLock BufferAutos
         While Not EstaMyAutoLavado(CodigoVehiculo)
            mon.Wait(BufferAutos)
         End While
         Dim i As Integer
         For i = 0 To BufferAutos.Length - 1
            If Not BufferAutos(i) Is Nothing Then
               If BufferAutos(i).getCodigoVehiculo = CodigoVehiculo Then
                  Exit For
               End If
            End If
         Next
         Dim MyAutoAux As Vehiculo = BufferAutos(i)
         BufferAutos(i) = Nothing
         Me.Lista.Items.Add("Cliente Saca Su Auto Lavado. Codigo: " & MyAutoAux.getCodigoVehiculo & " Estado: " & MyAutoAux.getEstadoVehiculo)
         mon.PulseAll(BufferAutos)
         Return MyAutoAux
      End SyncLock
   End Function

   Public Function HayAutoParaLavar() As Boolean
      Dim i As Integer
      For i = 0 To BufferAutos.Length - 1
         If Not BufferAutos(i) Is Nothing Then
            If BufferAutos(i).getEstadoVehiculo = 1 Then
               Return True
            End If
         End If
      Next
      Return False
   End Function

   Public Function EstoyLavandoUnAuto() As Boolean
      Dim i As Integer
      For i = 0 To BufferAutos.Length - 1
         If Not BufferAutos(i) Is Nothing Then
            If BufferAutos(i).getEstadoVehiculo = 2 Then
               Return True
            End If
         End If
      Next
      Return False
   End Function

   Public Function EstaMyAutoLavado(ByVal CodigoVehiculo As Integer) As Boolean
      Dim i As Integer
      For i = 0 To BufferAutos.Length - 1
         If Not BufferAutos(i) Is Nothing Then
            If BufferAutos(i).getEstadoVehiculo = 3 And BufferAutos(i).getCodigoVehiculo = CodigoVehiculo Then
               Return True
            End If
         End If
      Next
      Return False
   End Function

   Public Function EstaLlenoBufferAuto() As Boolean
      Dim i As Integer
      For i = 0 To BufferAutos.Length - 1
         If BufferAutos(i) Is Nothing Then
            Return False
         End If
      Next
      Return True
   End Function



   Public Function EstaVacioBufferAutos() As Boolean
      Dim i As Integer
      For i = 0 To BufferAutos.Length - 1
         If Not BufferAutos(i) Is Nothing Then
            Return False
         End If
      Next
      Return True
   End Function

   Public Function MyEstados() As ListBox
      Return Lista
   End Function
End Class

Bueno aca Complicamos un poco mas las cosas. Esta Clase cuenta con un Atributo Importante que es el Buffer Llamado BufferAutos el cual puede contener hasta 25 autos, otro Atributo tambien importante que vendria a ser mon que es del Monitor para que esta Clase se pueda comportar como tal. y un Atributo del Tipo ListBox para guardar los estados en una lista, para ir viendo como actuan los hilos.

El constructor recibe por parametro la Lista del Formulario la cual toma la referencia de esa lista para que el monitor pueda trabajar con ella.

Ahora pasamos a explicar los cuatro metodos importantes que entre si, parecen similares.

El Primer Metodo Llamado PonerAutoALavar(Auto), como veran recibe un auto Por Parametro (y si, si no que voy a Lavar), bien ahora voy a aclarar algo, todos sabran que al trabajar con hilos (Procesos que se ejecutan al mismo tiempo, tomemos lo que dije entre comillas debido a que se ejecutan al mismo si el equipo cuenta com mas de un procesador, caso contrario el micro le dara un tiempito de ejecucion a cada proceso, que en este caso no seria tan al mismo tiempo) bueno como venia contando cuando se trabajan con hilos estos puede querer intentar acceder a la memoria al mismo tiempo, y en el mismo lugar, lo cual podria provocar un desastre debido a que los dos procesos intentaria acceder a una misma region de momoria al mismo tiempo. Para este caso se Sincronizan partes de un metodo (SyncLock y End SyncLock) o un metodo completo si se quiere. Que es lo que hace esto, digamoslo asi, cada celda de la memoria tiene una cerradura y cada proceso tiene la llave de esa cerradura la cual solamente se abre y se cierra desde adentro, entonces el proceso entra a la celda, cierra con llave para que otro no pueda entrar y espere afuera hasta que este termine de hacer lo suyo, una ves que termina, este abre y el otro proceso que quedo esperando ingresa cerrando nuevamente la cerradura y asi sucesivamente. Bien Volvamos al Metodo la primera instruccion que tiene es el SyncLock BufferAutos, con esto bloqueo la memoria en este caso el Buffer, para que este Metedo pueda trabajar con el Buffer y no otro al mismo tiempo, luego hay un While EstaLlenoBufferAuto() y adentro hay un Wait(BufferAutos), bueno lo que hace esto es simple, Mientras este lleno el Buffer que Espere hasta que se desocupe algun Lugar, una ves que se desocupo el lugar con un For recorro el buffer buscando ese lugar vacio para poner mi auto, ahora se preguntaran porque no agrego el auto dentro del For y porque si hay muchos lugares vacios me va a poner ese mismo auto en todo esos lugares vacios, lo cual seria imponsible en un lavadero, una vez hecho esto, notifico a los hilos (PulseAll(BufferAutos)) que se ha producido un cambio en el estado, vendria a ser como decirle al que lava el auto, che te dejo el auto para que me lo laves, dentro de un rato lo paso a buscar. Desbloqueo la Memoria y Fin del Metodo. No es Complicado no cierto.

El Segundo Metodo Importante se Llama SacarAutoALavar() y devuelve un Auto. La Primera instruccion que encontramos se llama SyncLock que ya explicamos que hace, luego tenemos el While EstaVacioBufferAuto() Or Not HayAutoParaLavar() con un Wait(BufferAutos) dentro del While, lo que hace este ciclo es, Espera Mientras no tenga un Auto Para lavar o Espera Mientras no haya un auto sucio, a no ser de que nos guste mucho lavar y lavemos otro auto que este lavado cosa que no creo, una ves que haya un Auto y este sucio, Con Un For recorro el Buffer buscando ese auto sucio para cambiarle el estado a Lavando osea (2) el cambio de estado tambien se produce fuera del For debido a que me va a poner todos los auto sucios que encuentre en el estado 2 sin haber terminado de lavar uno cosa que en la realidad un empleado no podria lavar muchos autos al mismo tiempo. terminado esto notifico a los hilos que lave un auto, osea, le digo al Cliente que estoy Lavando su Auto, para que espere un ratito hasta que termine. Desbloqueo la Memoria (BufferAutos) y Listo.

El Tercer Metodo se Llama PonerAutoLavado(Auto) y envio en auto que estuve lavando, entonces Bloqueo la Memoria (SyncLock) para poder trabajar sin que otro hilo me moleste, luego hay un While Not EstoyLavandoUnAuto() con un Wait(BufferAutos) dentro, el Cual el Hilo Espera Mientras no este lavando un auto, este suena un poco raro pero esta bien, miren, si no estoy lavando un auto, es porque el auto o esta Sucio Estado 1 o ya esta Lavado Estado 3, si el Auto esta Lavado no le voy a poner de vuelta el Estado 3 de Lavado y si esta Sucio tampoco le Voy a Poner el Estado 3 de Lavado debido a que ni lo estuve Lavando, por eso pongo el estado 3 de lavado solamente a aquel aquel auto al que estuve lavando osea que tenga el Estado 2, si hay un Auto que estuve lavando Con un For Recorro el Buffer Buscando el auto Cuyo codigo de Auto Corresponda con ese auto, porque lo busco con el codigo y no con el estado, porque puede darce el caso (no es este) en que haya otro que tambien lave auto entonces puede pasar que yo le marque el auto que el estuvo lavando como lavado, cuando en realidad yo le tengo que poner el Estado de Auto Lavado al Auto que estuve lavando, y no a otro. Luego Notifico a los hilos del cambio de estado, osea, llamo al Cliente para que venga a buscar su auto, que ya esta Lavado. Desbloqueo La Memoria (End SyncLock) y Se Termino el Metodo.

El Cuarto y Ultimo Metodo se Llama SacarMyAutoLavado(Codigo) y me devuelve el auto, Bloqueo Memoria (SyncLock), despues tenemos un While Not EstaMyAutoLavado(Codigo) y un Wait(BufferAutos) dentro del Mismo, el Cual espera mientra el Cliente no tiene su auto lavado por eso el Codigo sino agarraria el primer auto que encuetra limpio y se lo llevaria, que no seria muy divertido, para el dueño de ese auto, una ves que encuentra su auto lavado recorre con un For buscando su Auto y se Lo Lleva notificando a los hilos que se llevo su auto, si no el que lava los autos podria pensar que le robaron el auto. Desbloqueo la memoria (End SyncLock) y Listo.

Los demas metodos no hace falta que los explique ya que son sencillos y entendibles.

 

Clase Form1 (Formulario)

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Código generado por el Diseñador de Windows Forms "

   Public Sub New()
      MyBase.New()

      'El Diseñador de Windows Forms requiere esta llamada.
      InitializeComponent()

      'Agregar cualquier inicialización después de la llamada a InitializeComponent()
      L = New Lavadero(Me.lstEstados) 'Creo un Lavadero y Le Paso el ListBox
   End Sub

   'Form reemplaza a Dispose para limpiar la lista de componentes.
   Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
         If Not (components Is Nothing) Then
            components.Dispose()
         End If
      End If
      MyBase.Dispose(disposing)
   End Sub

   'Requerido por el Diseñador de Windows Forms
   Private components As System.ComponentModel.IContainer

   'NOTA: el Diseñador de Windows Forms requiere el siguiente procedimiento
   'Puede modificarse utilizando el Diseñador de Windows Forms. 
   'No lo modifique con el editor de código.
   Friend WithEvents lstEstados As System.Windows.Forms.ListBox
   Friend WithEvents btnIniciar As System.Windows.Forms.Button
   <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
      Me.btnIniciar = New System.Windows.Forms.Button
      Me.lstEstados = New System.Windows.Forms.ListBox
      Me.SuspendLayout()
      '
      'btnIniciar
      '
      Me.btnIniciar.Font = New System.Drawing.Font("Microsoft Sans Serif", 12.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
      Me.btnIniciar.Location = New System.Drawing.Point(8, 8)
      Me.btnIniciar.Name = "btnIniciar"
      Me.btnIniciar.Size = New System.Drawing.Size(120, 32)
      Me.btnIniciar.TabIndex = 0
      Me.btnIniciar.Text = "Iniciar Hilos"
      '
      'lstEstados
      '
      Me.lstEstados.Font = New System.Drawing.Font("Microsoft Sans Serif", 12.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
      Me.lstEstados.ItemHeight = 20
      Me.lstEstados.Location = New System.Drawing.Point(8, 64)
      Me.lstEstados.Name = "lstEstados"
      Me.lstEstados.Size = New System.Drawing.Size(664, 204)
      Me.lstEstados.TabIndex = 1
      '
      'Form1
      '
      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
      Me.ClientSize = New System.Drawing.Size(680, 278)
      Me.Controls.Add(Me.lstEstados)
      Me.Controls.Add(Me.btnIniciar)
      Me.Name = "Form1"
      Me.Text = "Lavadero"
      Me.ResumeLayout(False)

   End Sub

#End Region
   Private L As Lavadero

   Private Sub btnIniciar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnIniciar.Click
      Dim Emp As New EmpleadoLavaAuto(L) 'Creo 1 Un Empleaeo Que Lava el Auto
      Dim C(100) As Cliente 'Declaro 100 Clientes
      Dim i As Integer
      For i = 0 To C.Length - 1
         C(i) = New Cliente(L) 'creo los 100 Empleados
         Dim HiloC As New Threading.Thread(AddressOf C(i).Run) 'Creo un Thread para Cada uno
         HiloC.Start() 'Inicio el Thread
      Next
      Dim hiloE As New Threading.Thread(AddressOf Emp.Run) 'Creo un Thread para el Empleado
      hiloE.Start() 'Inicio el Thread
   End Sub
End Class


Bueno este es el Formulario, Como veran no tiene muchas ciencia es muy simple tiene un Boton (btnIniciar) y una ListBox (lstEstados), con un For Creo los 100 Clientes que vendrian a ser 100 Threads y 1 empleado que vendria a ser otro Thread, tambien podria haber hecho un Cliente y que en el Metodo Run() tenga un for que valla hasta 100, pero no todos las Personas tienen 100 Autos.


Espacios de nombres usados en el código de este artículo:

System.Windows.Forms
System.Threading


ir al índice

Fichero con el código de ejemplo: Marcos_Ejemplo_Sencillo_Del_Uso_De_Threads.zip - 13 KB