Python Multihilo
El multihilo es similar a ejecutar varios programas diferentes al mismo tiempo. La ejecución multihilo tiene las siguientes ventajas:
- El uso de hilos permite poner las tareas que ocupan mucho tiempo en un programa en segundo plano.
- La interfaz de usuario puede ser más atractiva; por ejemplo, cuando el usuario hace clic en un botón para desencadenar el procesamiento de ciertos eventos, se puede mostrar una barra de progreso que indique el avance del procesamiento.
- La velocidad de ejecución del programa puede aumentar.
- En tareas que implican espera, como la entrada del usuario, la lectura y escritura de archivos y el envío y recepción de datos de red, los hilos resultan bastante útiles. En tales casos, podemos liberar algunos recursos valiosos, como la ocupación de memoria, etc.
Los hilos se diferencian de los procesos durante su ejecución. Cada proceso independiente tiene una entrada de ejecución del programa, una secuencia de ejecución ordenada y una salida del programa. Sin embargo, los hilos no pueden ejecutarse de forma independiente; deben depender de una aplicación, y es la aplicación la que proporciona el control de ejecución de múltiples hilos.
Cada hilo tiene su propio conjunto de registros de CPU, llamado contexto del hilo. Este contexto refleja el estado de los registros de CPU la última vez que el hilo se ejecutó.
Los registros de puntero de instrucción y puntero de pila son los dos registros más importantes del contexto del hilo. Los hilos siempre se ejecutan en el contexto del proceso; estas direcciones se usan para señalar la memoria en el espacio de direcciones del proceso propietario del hilo.
- Los hilos pueden ser adelantados (interrumpidos).
- Mientras otros hilos se están ejecutando, un hilo puede suspenderse temporalmente (también llamado dormir); esto es la cesión del hilo.
Comienza a aprender los hilos de Python
En Python, hay dos formas de usar hilos: con funciones o usando una clase para envolver el objeto del hilo.
Forma funcional: llamar a la función start_new_thread() del módulo thread para crear un nuevo hilo. La sintaxis es la siguiente:
thread.start_new_thread ( function, args[, kwargs] )
Descripción de parámetros:
- function - la función del hilo.
- args - argumentos pasados a la función del hilo; deben ser de tipo tupla.
- kwargs - parámetros opcionales.
Ejemplo (Python 2.0+)
El resultado de ejecutar el programa anterior es el siguiente:
Thread-1: Thu Jan 22 15:42:17 2009 Thread-1: Thu Jan 22 15:42:19 2009 Thread-2: Thu Jan 22 15:42:19 2009 Thread-1: Thu Jan 22 15:42:21 2009 Thread-2: Thu Jan 22 15:42:23 2009 Thread-1: Thu Jan 22 15:42:23 2009 Thread-1: Thu Jan 22 15:42:25 2009 Thread-2: Thu Jan 22 15:42:27 2009 Thread-2: Thu Jan 22 15:42:31 2009 Thread-2: Thu Jan 22 15:42:35 2009
La finalización de un hilo generalmente depende de la terminación natural de la función del hilo; también se puede llamar a thread.exit() dentro de la función del hilo, que lanza una excepción SystemExit, con el fin de salir del hilo.
Módulo de hilos
Python ofrece soporte para hilos a través de dos bibliotecas estándar: thread y threading. thread proporciona hilos de bajo nivel y primitivos, así como un bloqueo simple.
Otros métodos proporcionados por el módulo threading:
- threading.currentThread(): Devuelve la variable del hilo actual.
- threading.enumerate(): Devuelve una lista que contiene los hilos en ejecución. 'En ejecución' se refiere a después de que el hilo se inicia y antes de que finalice; no incluye los hilos antes de iniciarse ni después de terminarse.
- threading.activeCount(): Devuelve el número de hilos en ejecución; el mismo resultado que len(threading.enumerate()).
Además de los métodos, el módulo de hilos también proporciona la clase Thread para gestionar hilos. La clase Thread ofrece los siguientes métodos:
- run():Métodos que representan la actividad del hilo.
- start():Inicia la actividad del hilo.
- join([time]):Espera hasta que el hilo termine. Esto bloquea al hilo llamante hasta que el hilo termina, ya sea por salida normal o por lanzar una excepción no controlada, o hasta que se produce un tiempo de espera opcional.
- isAlive():Devuelve si el hilo está activo.
- getName():Devuelve el nombre del hilo.
- setName():Establece el nombre del hilo.
Crear hilos usando el módulo Threading
Para crear hilos con el módulo Threading, hereda directamente de threading.Thread y luego sobrescribe el método __init__ y el método run:
Ejemplo (Python 2.0+)
El resultado de ejecutar el programa anterior es el siguiente:
Starting Thread-1 Starting Thread-2 Exiting Main Thread Thread-1: Thu Mar 21 09:10:03 2013 Thread-1: Thu Mar 21 09:10:04 2013 Thread-2: Thu Mar 21 09:10:04 2013 Thread-1: Thu Mar 21 09:10:05 2013 Thread-1: Thu Mar 21 09:10:06 2013 Thread-2: Thu Mar 21 09:10:06 2013 Thread-1: Thu Mar 21 09:10:07 2013 Exiting Thread-1 Thread-2: Thu Mar 21 09:10:08 2013 Thread-2: Thu Mar 21 09:10:10 2013 Thread-2: Thu Mar 21 09:10:12 2013 Exiting Thread-2
Sincronización de hilos
Si varios hilos modifican un mismo dato de forma conjunta, pueden aparecer resultados impredecibles. Para garantizar la corrección de los datos, es necesario sincronizar los múltiples hilos.
Usando Lock y Rlock del objeto Thread se puede lograr una sincronización sencilla de hilos. Estos dos objetos tienen métodos acquire y release. Para aquellos datos que solo deben permitir que un hilo los administre a la vez, se puede colocar la operación entre los métodos acquire y release. De la siguiente manera:
La ventaja del multihilo es que puede ejecutar varias tareas al mismo tiempo (al menos así lo parece). Pero cuando los hilos necesitan compartir datos, puede existir el problema de la falta de sincronización de datos.
Consideremos el caso en que todos los elementos de una lista son 0; el hilo 'set' cambia todos los elementos de atrás hacia adelante a 1, mientras que el hilo 'print' se encarga de leer la lista de adelante hacia atrás e imprimirla.
Entonces, es posible que cuando el hilo 'set' empiece a modificar, el hilo 'print' imprima la lista, y la salida resulte mitad 0 y mitad 1. Esto es la falta de sincronización de los datos. Para evitar esta situación, se introduce el concepto de bloqueo.
El bloqueo tiene dos estados: bloqueado y desbloqueado. Siempre que un hilo, como 'set', vaya a acceder a datos compartidos, primero debe obtener el bloqueo; si otro hilo, como 'print', ya ha obtenido el bloqueo, entonces se detiene al hilo 'set', es decir, un bloqueo por sincronización; cuando el hilo 'print' termina de acceder y libera el bloqueo, se deja que el hilo 'set' continúe.
Con este tratamiento, al imprimir la lista se mostrarán todos 0 o todos 1, y ya no aparecerá la incómoda situación de mitad 0 y mitad 1.
Ejemplo (Python 2.0+)
Cola de prioridad de hilos (Queue)
El módulo Queue de Python proporciona clases de cola sincronizadas y seguras para hilos, incluyendo la cola FIFO (primero en entrar, primero en salir) Queue, la cola LIFO (último en entrar, primero en salir) LifoQueue, y la cola de prioridad PriorityQueue. Todas estas colas implementan primitivas de bloqueo y pueden usarse directamente en varios hilos. Las colas pueden usarse para lograr la sincronización entre hilos.
Métodos comunes en el módulo Queue:
- Queue.qsize() devuelve el tamaño de la cola
- Queue.empty() devuelve True si la cola está vacía, de lo contrario False
- Queue.full() devuelve True si la cola está llena, de lo contrario False
- Queue.full corresponde con el tamaño de maxsize
- Queue.get([block[, timeout]]) obtiene un elemento de la cola; timeout es el tiempo de espera
- Queue.get_nowait() equivale a Queue.get(False)
- Queue.put(item, block=True, timeout=None) escribe el elemento en la cola; timeout es el tiempo de espera
- Queue.put_nowait(item) equivale a Queue.put(item, False)
- Queue.task_done() Después de completar un trabajo, la función Queue.task_done() envía una señal a la cola de que la tarea se ha completado.
- Queue.join() En realidad, significa esperar hasta que la cola esté vacía antes de realizar otras operaciones.
Ejemplo (Python 2.0+)
El resultado de ejecutar el programa anterior es:
Starting Thread-1 Starting Thread-2 Starting Thread-3 Thread-1 processing One Thread-2 processing Two Thread-3 processing Three Thread-1 processing Four Thread-2 processing Five Exiting Thread-3 Exiting Thread-1 Exiting Thread-2 Exiting Main ThreadOtras extensiones