Python - Programación multiproceso

Ejecutar varios subprocesos es similar a ejecutar varios programas diferentes al mismo tiempo, pero con los siguientes beneficios:

  • Varios subprocesos dentro de un proceso comparten el mismo espacio de datos con el subproceso principal y, por lo tanto, pueden compartir información o comunicarse entre sí más fácilmente que si fueran procesos separados.

  • Los subprocesos a veces se denominan procesos ligeros y no requieren mucha sobrecarga de memoria; son más baratos que los procesos.

Un hilo tiene un comienzo, una secuencia de ejecución y una conclusión. Tiene un puntero de instrucción que realiza un seguimiento de dónde se está ejecutando actualmente dentro de su contexto.

  • Se puede adelantar (interrumpir)

  • Se puede poner en espera temporalmente (también conocido como inactivo) mientras se ejecutan otros subprocesos; esto se llama rendimiento.

Comenzando un nuevo hilo

Para generar otro hilo, debe llamar al siguiente método disponible en el módulo del hilo :

thread.start_new_thread ( function, args[, kwargs] )

Esta llamada al método permite una forma rápida y eficiente de crear nuevos subprocesos tanto en Linux como en Windows.

La llamada al método regresa inmediatamente y el subproceso secundario comienza y llama a la función con la lista pasada de argumentos . Cuando la función regresa, el hilo termina.

Aquí, args es una tupla de argumentos; use una tupla vacía para llamar a la función sin pasar ningún argumento. kwargs es un diccionario opcional de argumentos de palabras clave.

Ejemplo

#!/usr/bin/python

import thread
import time

# Define a function for the thread
def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print "%s: %s" % ( threadName, time.ctime(time.time()) )

# Create two threads as follows
try:
   thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print "Error: unable to start thread"

while 1:
   pass

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Aunque es muy eficaz para subprocesos de bajo nivel, el módulo de subprocesos es muy limitado en comparación con el módulo de subprocesamiento más nuevo.

El módulo de subprocesamiento

El módulo de subprocesos más nuevo incluido con Python 2.4 proporciona un soporte de alto nivel mucho más poderoso para subprocesos que el módulo de subprocesos discutido en la sección anterior.

El módulo de subprocesos expone todos los métodos del módulo de subprocesos y proporciona algunos métodos adicionales:

  • threading.activeCount() - Devuelve el número de objetos de hilo que están activos.

  • threading.currentThread() - Devuelve el número de objetos de hilo en el control de hilo de la persona que llama.

  • threading.enumerate() - Devuelve una lista de todos los objetos de hilo que están actualmente activos.

Además de los métodos, el módulo de subprocesos tiene la clase Thread que implementa subprocesos. Los métodos proporcionados por la clase Thread son los siguientes:

  • run() - El método run () es el punto de entrada para un hilo.

  • start() - El método start () inicia un hilo llamando al método run.

  • join([time]) - El join () espera a que terminen los hilos.

  • isAlive() - El método isAlive () comprueba si un hilo aún se está ejecutando.

  • getName() - El método getName () devuelve el nombre de un hilo.

  • setName() - El método setName () establece el nombre de un hilo.

Creación de hilos que utilizan Threading de módulo

Para implementar un nuevo hilo usando el módulo de subprocesos, debe hacer lo siguiente:

  • Defina una nueva subclase de la clase Thread .

  • Anule el método __init __ (self [, args]) para agregar argumentos adicionales.

  • Luego, anule el método run (self [, args]) para implementar lo que debería hacer el hilo cuando se inicia.

Una vez que haya creado la nueva subclase Thread , puede crear una instancia de la misma y luego iniciar un nuevo hilo invocando start () , que a su vez llama al método run () .

Ejemplo

#!/usr/bin/python

import threading
import time

exitFlag = 0

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print "Starting " + self.name
      print_time(self.name, 5, self.counter)
      print "Exiting " + self.name

def print_time(threadName, counter, delay):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print "%s: %s" % (threadName, time.ctime(time.time()))
      counter -= 1

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

print "Exiting Main Thread"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Sincronizar hilos

El módulo de subprocesos proporcionado con Python incluye un mecanismo de bloqueo fácil de implementar que le permite sincronizar subprocesos. Se crea un nuevo bloqueo llamando al método Lock () , que devuelve el nuevo bloqueo.

El método de adquisición (bloqueo) del nuevo objeto de bloqueo se utiliza para forzar a que los subprocesos se ejecuten sincrónicamente. El parámetro de bloqueo opcional le permite controlar si el hilo espera para adquirir el bloqueo.

Si el bloqueo se establece en 0, el hilo regresa inmediatamente con un valor de 0 si el bloqueo no se puede adquirir y con un 1 si se adquirió el bloqueo. Si el bloqueo se establece en 1, el hilo se bloquea y espera a que se libere el bloqueo.

El método release () del nuevo objeto de bloqueo se utiliza para liberar el bloqueo cuando ya no es necesario.

Ejemplo

#!/usr/bin/python

import threading
import time

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print "Starting " + self.name
      # Get lock to synchronize threads
      threadLock.acquire()
      print_time(self.name, self.counter, 3)
      # Free lock to release next thread
      threadLock.release()

def print_time(threadName, delay, counter):
   while counter:
      time.sleep(delay)
      print "%s: %s" % (threadName, time.ctime(time.time()))
      counter -= 1

threadLock = threading.Lock()
threads = []

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

# Add threads to thread list
threads.append(thread1)
threads.append(thread2)

# Wait for all threads to complete
for t in threads:
    t.join()
print "Exiting Main Thread"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Starting Thread-1
Starting Thread-2
Thread-1: Thu Mar 21 09:11:28 2013
Thread-1: Thu Mar 21 09:11:29 2013
Thread-1: Thu Mar 21 09:11:30 2013
Thread-2: Thu Mar 21 09:11:32 2013
Thread-2: Thu Mar 21 09:11:34 2013
Thread-2: Thu Mar 21 09:11:36 2013
Exiting Main Thread

Cola de prioridad multiproceso

El módulo de cola le permite crear un nuevo objeto de cola que puede contener un número específico de elementos. Existen los siguientes métodos para controlar la cola:

  • get() - El get () elimina y devuelve un elemento de la cola.

  • put() - El puesto agrega el artículo a una cola.

  • qsize() - El qsize () devuelve el número de elementos que están actualmente en la cola.

  • empty()- El vacío () devuelve Verdadero si la cola está vacía; de lo contrario, Falso.

  • full()- full () devuelve True si la cola está llena; de lo contrario, Falso.

Ejemplo

#!/usr/bin/python

import Queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
   def __init__(self, threadID, name, q):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.q = q
   def run(self):
      print "Starting " + self.name
      process_data(self.name, self.q)
      print "Exiting " + self.name

def process_data(threadName, q):
   while not exitFlag:
      queueLock.acquire()
         if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print "%s processing %s" % (threadName, data)
         else:
            queueLock.release()
         time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1

# Create new threads
for tName in threadList:
   thread = myThread(threadID, tName, workQueue)
   thread.start()
   threads.append(thread)
   threadID += 1

# Fill the queue
queueLock.acquire()
for word in nameList:
   workQueue.put(word)
queueLock.release()

# Wait for queue to empty
while not workQueue.empty():
   pass

# Notify threads it's time to exit
exitFlag = 1

# Wait for all threads to complete
for t in threads:
   t.join()
print "Exiting Main Thread"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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 Thread