¿Cómo utilizar el threading en Python?

Estoy tratando de entender el threading en Python. He mirado la documentación y los ejemplos, pero francamente, muchos ejemplos son demasiado sofisticados y me cuesta entenderlos.

¿Cómo se muestran claramente las tareas que se dividen para el multihilo?

He aquí un ejemplo sencillo: hay que probar unas cuantas URLs alternativas y devolver el contenido de la primera que responda.

import Queue
import threading
import urllib2

# called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Este es un caso en el que se utiliza el threading como una simple optimización: cada subhilo está esperando a que una URL se resuelva y responda, para poner su contenido en la cola; cada hilo es un demonio (no mantendrá el proceso si el hilo principal termina - eso es más común que no); el hilo principal inicia todos los subhilos, hace un get en la cola para esperar hasta que uno de ellos haya hecho un put, entonces emite los resultados y termina (lo que hace caer cualquier subhilo que pueda estar todavía en ejecución, ya que son hilos demonio).

El uso apropiado de los hilos en Python está invariablemente conectado a las operaciones de E/S (ya que CPython no utiliza múltiples núcleos para ejecutar tareas limitadas a la CPU de todos modos, la única razón para el uso de hilos es no bloquear el proceso mientras hay una espera para alguna E/S). Las colas son casi siempre la mejor manera de distribuir el trabajo a los hilos y/o recoger los resultados del trabajo, por cierto, y son intrínsecamente seguras para los hilos, por lo que te ahorran la preocupación por los bloqueos, las condiciones, los eventos, los semáforos y otros conceptos de coordinación/comunicación entre hilos.

Comentarios (14)

NOTA: Para la paralelización real en Python, debe utilizar el módulo multiprocesamiento para bifurcar múltiples procesos que se ejecuten en paralelo (debido al bloqueo global del intérprete, los hilos de Python proporcionan intercalación, pero en realidad se ejecutan en serie, no en paralelo, y sólo son útiles cuando se intercalan operaciones de E/S).

Sin embargo, si sólo buscas intercalar (o estás haciendo operaciones de E/S que pueden ser paralelizadas a pesar del bloqueo global del intérprete), entonces el módulo threading es el lugar para empezar. Como ejemplo realmente sencillo, consideremos el problema de sumar un rango grande sumando subrangos en paralelo:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i

thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()  
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Tenga en cuenta que el anterior es un ejemplo muy estúpido, ya que no hace absolutamente ninguna E/S y se ejecutará en serie aunque intercalado (con la sobrecarga añadida de cambio de contexto) en CPython debido al bloqueo global del intérprete.

Comentarios (8)

Como otros mencionaron, CPython puede usar hilos sólo para las esperas I\O debido a GIL. Si quieres beneficiarte de los múltiples núcleos para las tareas ligadas a la CPU, utiliza multiproceso:

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Comentarios (5)