jueves, 24 de noviembre de 2011

Threads en .NET, uso de la clase Interlocked

Hace unos días que estoy lidiando con hilos para una aplicación .NET en el trabajo, así que me he decidido a hacer un breve post con un pequeño ejemplo una aplicación multihilo.

Prácticamente todos los entornos de desarrollo permiten el uso de "threads" en sus aplicaciones y efectivamente este es el caso de .NET. Desgraciadamente cada tecnología tiene una ideología distinta para acceder a los hilos, por ejemplo en Java para usar "threads" se debe crear una clase que implemente la interfaz Thread, o en C bajo Unix se puede usar el clásico "fork".

En .NET cuando lanzamos un hilo, le asociamos una función, de manera que una vez se arranca el hilo, ejecutará esta función. En cierto modo nos simplifica la forma de programar, pues solo tendremos que programar la función y después simplemente crearemos un hilo para ejecutarla.

El ejemplo que voy a utilizar es muy sencillo, un "thread" principal se encarga de lanzar varios hilos, cuyo número limitaremos, que mostrarán por consola un año de un array, sin que dos hilos cojan el mismo elemento del array.

El número de hilos abiertos los controlaremos con un semáforo. Para poder manejar este semáforo, nos apoyaremos en la clase Interlocked, en concreto los métodos Interlocked.Increment([semaforo]) y Interlocked.Decrement([semaforo]). Estos métodos nos permiten incrementar o reducir en uno el valor del semáforo de manera "thread-safe", es decir, que todos los hilos actualizan a la vez el valor del semáforo. El semáforo será un atributo "shared" de la clase, lo cual permite que los hilos lo compartan.

Os muestro el código a continuación:

    'Semaforo
    Private Shared opened As Integer
    Private years As Integer() = {1984, 1985, 1986, 1987, 1988, 1989, 1990,
                                  1991, 1992, 1993, 1994, 1995, 1996, 1997,
                                  1998, 1999, 2000, 2001, 2002, 2003, 2004,
                                  2005, 2006, 2007, 2008, 2009, 2010, 2011}

    Private Sub threadLauncher(threadLimit As Int16)
        opened = 0
        Dim i As Integer = 0

        'Mientras haya algún thread abierto, o queden años por procesar
        While (Interlocked.Read(opened) > 0 Or i < years.Count)
            'Si el número de hilos abiertos es menor que el máximo, y quedan
            'años por procesar, creamos un nuevo hilo
            If (Interlocked.Read(opened) < threadLimit And i < years.Count) Then
                Dim workerThread As New Thread(AddressOf processYear)

                workerThread.Start(i)
                i += 1

                'Indicamos a todos los hilos abiertos, que hemos reservado uno nuevo
                Interlocked.Increment(opened)
            End If
            Thread.Sleep(10)
        End While
    End Sub

    Private Sub processYear(index As Object)
        Dim i As Integer = DirectCast(index, Integer)
        Dim str As String = String.Format("Procesando año {0}", years(i))

        'Esperamos un tiempo aleatorio en cada hilo, para que no se muestren en orden
        Thread.Sleep(New Random().Next(100))

        System.Console.WriteLine(str)

        'Indicamos al hilo principal, que se acaba de liberar un hilo.
        Interlocked.Decrement(opened)
    End Sub

 

Al ejecutar el método threadLauncher con un límite de 5, he obtenido los siguientes resultados:
Procesando año 1989
Procesando año 1988
Procesando año 1984
Procesando año 1986
Procesando año 1990
Procesando año 1987
Procesando año 1993
Procesando año 1992
......


Como era de esperar, los hilos acaban en distintos momentos y por lo tanto no siguen un orden establecido, pero no obstante lo que no ocurre es que dos hilos compitan por el mismo recurso, en este caso, que dos hilos procesen el mismo año.

Y con esto acabamos el artículo sobre hilos en .NET, espero que os haya servido como ilustración para saber por donde empezar a lidiar con los hilos.

No hay comentarios:

Publicar un comentario