Ejemplo Sencillo del Uso de Threads Simulacion de un Lavadero de Autos Fecha: de publicación (08-02-2005)
|
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 ClassEsta 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 ClassBueno 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
Fichero con el código de ejemplo: Marcos_Ejemplo_Sencillo_Del_Uso_De_Threads.zip - 13 KB