Librerias más usadas en los distintos lenguajes

De Departamento de Informatica
Saltar a: navegación, buscar

Contenido

Contenidos

Librerias Comunes en python

La ejecución de los threads en Python está controlada por el GIL (Global Interpreter Lock) de forma que sólo un thread puede ejecutarse a la vez, independientemente del número de procesadores con el que cuente la máquina. Esto posibilita que el escribir extensiones en C para Python sea mucho más sencillo, pero tiene la desventaja de limitar mucho el rendimiento, por lo que a pesar de todo, en Python, en ocasiones nos puede interesar más utilizar procesos que threads, que no sufren de esta limitación.

El trabajo con threads en Python se lleva a cabo mediante el modulo threads. Este modulo es opcional y dependiente de la plataforma, y puede ser necesario, aunque no es común, recompilar el intérprete para añadir el soporte de threads. Además de threads contamos contamos con el módulo threading que se apoya en el primero para proporcionarnos una API de más alto nivel, más completa, y orientada a objetos. El módulo threading se basa ligeramente en el modelo de threads de java. El módulo de threading contiene una clase Thread que debemos extender para crear nuestros propios hilos de ejecución. El método run contendrá el código que queremos que ejecute el thread. Si queremos especificar nuestro propio constructor, este deberá llamar a threading.Thread.__init__(self) para inicializar el objeto correctamente.


import threading

class MiThread(threading.Thread):
     def __init__(self, num):
         threading.Thread.__init__(self)
         self.num = num

     def run(self):
         print "Soy el hilo", self.num

Para que el thread comience a ejecutar su código basta con crear una instancia de la clase que acabamos de definir y llamar a su método start. El código del hilo principal y el del que acabamos de crear se ejecutarán de forma concurrente.


print "Soy el hilo principal"

for i in range(0, 10):
    t = MiThread(i)
    t.start()
    t.join()

El método join se utiliza para que el hilo que ejecuta la llamada se bloquee hasta que finalice el thread sobre el que se llama. En este caso se utiliza para que el hilo principal no termine su ejecución antes que los hijos, lo cuál podría resultar en algunas plataformas en la terminación de los hijos antes de finalizar su ejecución. El método join puede tomar como parámetro un número en coma flotante indicando el número máximo de segundos a esperar. Si se intenta llamar al método start para una instancia que ya se está ejecutando, obtendremos una excepción. La forma recomendada de crear nuevos hilos de ejecución consiste en extender la clase Thread, como hemos visto, aunque también es posible crear una instancia de Thread directamente, e indicar como parámetros del constructor una clase ejecutable (una clase con el método especial __call__) o una función a ejecutar, y los argumentos en una tupla (parámetro args) o un diccionario (parámetro kwargs).


import threading

def imprime(num):
    print "Soy el hilo", num

print "Soy el hilo principal"

for i in range(0, 10):
    t = threading.Thread(target=imprime, args=(i, ))
    t.start()

Además de los parámetros target, args y kwargs también podemos pasar al constructor un parámetro de tipo cadena name con el nombre que queremos que tome el thread (el thread tendrá un nombre predeterminado aunque no lo especifiquemos); un parámetro de tipo booleano verbose para indicar al módulo que imprima mensajes sobre el estado de los threads para la depuración y un parámetro group, que por ahora no admite ningún valor pero que en el futuro se utilizará para crear grupos de threads y poder trabajar a nivel de grupos. Para comprobar si un thread sigue ejecutándose, se puede utilizar el método isAlive. También podemos asignar un nombre al hilo y consultar su nombre con los métodos setName y getName. Mediante la función threading.enumerate obtendremos una lista de los objetos Thread que se están ejecutando, incluyendo el hilo principal (podemos comparar el objeto Thread con la variable main_thread para comprobar si se trata del hilo principal) y con threading.activeCount podemos consultar el número de threads ejecutándose. Los objetos Thread también cuentan con un método setDaemon que toma un valor booleano indicando si se trata de un demonio. La utilidad de esto es que si solo quedan threads de tipo demonio ejecutándose, la aplicación terminará automáticamente, terminando estos threads de forma segura. Por último tenemos en el módulo threading una clase Timer que hereda de Thread y cuya utilidad es la de ejecutar el código de su método run después de un periodo de tiempo indicado como parámetro en su constructor. También incluye un método cancel mediante el que cancelar la ejecución antes de que termine el periodo de espera.


Librerias Comunes en C/C++

Dentro de las múltiples librerias en C o C++ para trabajar con threads, POSIX o como uno la incluye en el código pthread.h es una de las más populares[1].

Ya hicimos uso de esta libreria en el ejemplo de threads muchos a muchos[Link M:M], pero ahora reforzaremos la idea con un ejemplo más simple.


#include <pthread.h> //Biblioteca thread
#include <stdio.h>

/* Esta es nuestra función thread.
void *threadFunc(void *arg)
{
	char *str;
	int i = 0;

	str=(char*)arg;

	while(i < 110 )
	{
		usleep(1);
		printf("threadFunc dice: %s\n",str);
		++i;
	}

	return NULL;
}

int main(void)
{
	pthread_t pth;	// Este es nuestro identificador de  thread.
	int i = 0;

	pthread_create(&pth,NULL,threadFunc,"foo");
	
	while(i < 100)
	{
		usleep(1);
		printf("El programa principal está ejecutandose...\n");
		++i;
	}

	printf("Programa principal esperando thread para terminar...\n");
	pthread_join(pth,NULL);

	return 0;
}

Mencionando otras, siguiendo la lista sugerida en el primer link tenemos a Boost que en realidad es un conjunto de bibliotecas que extienden la funcionalidad del lenguaje c++ la cual tiene muy buena documentación[2] y es posible apreciar código de ejemplo.

Librerias Comunes en Java

En Java el soporte para programación multihebra viene incorporado por lo que no es necesario el uso de una librería, estas se pueden implementar de 2 maneras:

  • Se puede implementar la interfaz "Runnable"
  • Se puede extender la clase "Thread"

Crear una hebra implmentando Runnable

La manera mas facil de crear una hebra es implementar una clase que implemente la interfaz Runnable.

Para implementar Runnable, una clase solo necesita implementar un solo metodo llamado run(), que es declaro asi:

public void run( )

Se definira el codigo que constituye la nueva hebra dentro del metodo run(). Es importante entender que run() puede llamar otros metodos, usar otras clases, declarar variables, como cualquier hebra principal.

Despues de creada la clase que implementa Runnable, se debera instanciar el objeto de tipo Thread dentro de esa clase. Thread define varios constructores. El que se usara se muestra aqui:

Thread(Runnable threadOb, String threadName);

Aqui threadOb es la instacia de la clase que implementa la interfaz Runnable y el nombre de la nueva hebra esta especificado en threadName

Despues de que se crea la hebra, esta no empezara a ejecutarse hasta que se llame su metodo start(), el cual esta declarado dentro de Thread. El metodo start() se muestra aqui:

void start( );

A continuación un ejemplo en el cual se crea una hebra y se ejecuta utilizando el método anterior.

// Crea una nueva hebra
class NewThread implements Runnable {
   Thread t;
   NewThread() {
      // Crea una nueva, segunda hebra
      t = new Thread(this, "Demo Thread");
      System.out.println("Child thread: " + t);
      t.start(); // Ejecuta la hebra
   }
   
   // Este es el punto de partida de la segunda hebra
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
            // Deja a la hebra esperar por un momento
            Thread.sleep(500);
         }
     } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
     }
     System.out.println("Exiting child thread.");
   }
}

class ThreadDemo {
   public static void main(String args[]) {
      new NewThread(); // crea una nueva hebra
      try {
         for(int i = 5; i > 0; i--) {
           System.out.println("Main Thread: " + i);
           Thread.sleep(1000);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

Crear una hebra extendiendo Thread

La segunda manera de crear una hebra is crear una nueva clase que extienda Thread, y luego crear una instancia de esa clase.

La clase que extiende debe sobrescribir el método run(), el cual es el punto de partida para la nueva hebra. También debe llamar a start() para empezar la ejecución de la nueva hebra.

Aquí el programa anterior rescrito usando la extensión de Thread:

// Crea una segunda hebra extendiendo Thread
class NewThread extends Thread {
   NewThread() {
      // Create a new, second thread
      super("Demo Thread");
      System.out.println("Child thread: " + this);
      start(); // Ejecuta la hebra
   }

   // punto de partida para la segunda hebra
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
			//  Deja a la hebra esperar por un momento
            Thread.sleep(500);
         }
      } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
      }
      System.out.println("Exiting child thread.");
   }
}

class ExtendThread {
   public static void main(String args[]) {
      new NewThread(); // crea una nueva hebra
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Main Thread: " + i);
            Thread.sleep(1000);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

Threads en CUDA

CUDA es una plataforma de computación paralela inventada por NVIDIA , que hace uso de las tarjetas de video(GPU) , y en muchos casos el rendimiento de las máquinas se incrementa de forma drástica, lo que ha permitido que la computación paralela muestre grandes avances en los últimos años.

Logo Característico de CUDA

Su forma nativa de programación se encuentra como una extensión del lenguaje C, aunque también se puede trabajar como una extensión de python mediante PyCuda[3] y asimismo es posible trabajar también de una forma más abstracta con PyOpencl[4] esta última es indicada por que si bien no se trabaja directamente con cuda, si ayuda mucho a entender la lógica de la computación paralela de buena manera.

¿Cómo funciona?
Comparación entre GPU y CPU

Como se puede apreciar en la comparación entre una unidad CPU y una GPU, esta última se destaca por poseer una gran cantidad de pequeñas unidades aritméticas (ALU), lo que permite muchos calculos de manera paralela, como por ejemplo el calcular o determinar un color por cada pixel de la pantalla.

GPU en detalle

Para ejecutar un programa, la GPU ejecuta un kernel(esto es, un grupo de trabajo) y cada kernel contiene grupos independientes de ALUS, como es posible de apreciar en la figura. Cada bloque está compuesto de threads y estos threads por lo general trabajan todos juntos de manera paralela para obtener un valor.

Esto es necesario de comprender, porque nos dará una idea más clara cuando ejecutemos nuestro código de prueba.

Instalación

Para poder utilizar CUDA en nuestros equipos es necesario disponer de una tarjeta nvidia y descargar el framework de trabajo desde la página de NVIDIA[5]

Ahora bien, si no se dispone de una tarjeta nvidia o esta es muy antigua, podemos programar en este lenguaje usando el servidor GPU que dispone labcomp[6], entre otros.

Código de Ejemplo

A continuación analizaremos un programa que toma un arreglo y eleva al cuadrado cada elemento.

#include "stdafx.h"
#include <stdio.h>
#include <cuda.h>


// Kernel que se ejecuta en el dispositivo CUDA
__global__ void square_array(float *a, int N)
{
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx<N) a[idx] = a[idx] * a[idx];
}



// La rutina principal que corre en el host
int main(void)
{

  float *a_h, *a_d;  // Punteros al host & matrices de dispositivo
  const int N = 10;  // Numero de elementos en el array.
  size_t size = N * sizeof(float);
  a_h = (float *)malloc(size);        // Asignamos el arreglo al host
  cudaMalloc((void **) &a_d, size);   // Asignamos el arreglo al dispositivo CUDA


  // Inicializamos el arreglo del host y lo copiamos hacia la tarjeta
  for (int i=0; i<N; i++) a_h[i] = (float)i;
  cudaMemcpy(a_d, a_h, size, cudaMemcpyHostToDevice);


  // realizamos el cálculo
  int block_size = 4;
  int n_blocks = N/block_size + (N%block_size == 0 ? 0:1);
  square_array <<< n_blocks, block_size >>> (a_d, N);


  // Retorna el resultado desde la tarjeta
  cudaMemcpy(a_h, a_d, sizeof(float)*N, cudaMemcpyDeviceToHost);


  // Muestra los resultados
  for (int i=0; i<N; i++) printf("%d %f\n", i, a_h[i]);

  // Limpia
  free(a_h); cudaFree(a_d);

}

Ahora si a nuestro archivo le llamamos arreglo.cu, siendo cu la extensión de cuda, en nuestra consola escribimos lo siguiente:

nvcc -c arreglo.cu

Y luego ejecutamos.

Referencias

CUDA Starting Guide

Herramientas personales
Espacios de nombres
Variantes
Acciones
Navegación
Herramientas