UNIDAD III

Programación Concurrente

Profesor Miguel Cantillana

INS240 – Lenguajes de Programación
Ingeniería en Computación e Informática
Semestre 2017-1

Multiprocessing & Asincronía

Multiprocessing

¿Que es Multiprocessing?

  • Es un paquete que permite crear nuevos procesos utilizando un API similar a la del módulo threading.
  • Utiliza subprocesos en lugar de hilos (threads), permite llevar a cabo varias operaciones concurrentes
    • Sin las limitaciones del Global Interpreter Lock.
    • Corre en sistemas Unix y Windows.

 

Pero primero, ¿por qué necesitamos Multiprocessing?

  • La diferencia entre hilo y proceso es fácil de reconocer
    • Un hilo ocurre dentro del espacio de memoria de un programa
    • Un proceso es una copia completa del programa

 

¿Threads o procesos?

  • Un hilo ocurre dentro del espacio de memoria de un programa.
  • Un proceso es una copia completa del programa.
  • Ambos administran los recursos de formas diferentes.
  • Mientras que los diferentes procesos de un mismo programa ocupan diferentes espacios en la memoria, diferentes hilos comparten un mismo espacio.
  • Thread evita el aprovechamiento de las múltiples CPUs del sistema.
  • Multiprocessing incluye módulos para intercambiar objetos entre los procesos (incluyendo dos métodos de comunicación), compartir memoria (poco recomendado) y sincronizarlos.

 

La idea es sencilla: características

  • En vez de usar threads, usamos procesos enteros, y evitamos restricciones de GIL.
  • Procesos independientes corriendo en tantas CPUs como haga falta.
  • Las APIs emulan las del módulo estándar de threading, con lo que las aplicaciones se escriben sin cambios a penas respecto al modelo anterior.
  • El módulo implementa comunicación entre procesos, colas de tareas y toda la magia de sincronización que provee el módulo de threading.

 

Tener en cuenta

  • Los hilos son rápidos de crear y destruir y consumen poca memoria.
  • Los procesos son lentos de crear y destruir además de que requieren clonar el espacio de memoria del programa en otro lugar de la RAM, y esto es lento.
  • Lanzar un proceso, claro, es siempre más costoso que iniciar un thread, aunque en Linux (no sé como será la cosa en otro unices) es razonablemente rápido.
  • Los cambios de contexto pueden ser costos en ambos casos.
  • Multiprocessing es una buena opción a la hora de buscar computo!.

 

¿Cómo empezamos a usar Multiprocessing?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from multiprocessing import Process

def f(x):
    print x*x
    # return x*x

if __name__ == '__main__':

    processes = []

    for i in range(5):

        processes.append(Process(target=f, args=(i,)))
        processes[i].start()
        print "Proceso %d lanzado." % (i + 1)

    for process in processes:
        process.join()
        
    print u"La ejecución a concluído."

proc_1.py

¿Cómo empezamos a usar Multiprocessing? (ii)


import time
import random
import multiprocessing

class Tarea:
    
    def __init__(self, cid):
        self.__cid=cid
        print("HIJO {0} - Nace".format(self.__cid))
    
    def __del__(self):
        print("HIJO {0} - Muere".format(self.__cid))
    
    def run(self):
        
        # Generamos un tiempo de espera aleatorio
        s=1+int(10*random.random())
        
        print("HIJO {0} - Inicio (Durmiendo {1} segundos)".format(self.__cid,s))
        time.sleep(s)
        print("HIJO {0} - Fin".format(self.__cid))

# Creamos la piscina (Pool)
piscina = []
for i in range(1,5):
    print("PADRE: creando HIJO {0}".format(i))
    piscina.append(multiprocessing.Process(name="Proceso {0}".format(i), target=Tarea(i).run))

# Arrancamos a todos los hijos
print("PADRE: arrancando hijos")
for proceso in piscina:
    proceso.start()

print("PADRE: esperando a que los procesos hijos hagan su trabajo")
for proceso in piscina:
    proceso.join()

print("PADRE: todos los hijos han terminado, cierro")

proc_2.py

¿Cómo empezamos a usar Multiprocessing? (iii)

proc_2.py

  1. Que el join() de los procesos se hace en el orden en que los procesos están almacenados en la piscina (Pool), eso NO es “Multiproceso” ya que el proceso 4 podría haber terminado primero y estaría esperando a ser recogido por el padre, pero el padre lo ha recogido en último lugar en ambas ejecuciones.
  2. Que el proceso en sí (objeto) sigue ocupando memoria porque todavía no ha sido destruido por Python, esto se debe a que sigue vinculado a la piscina (lista). Para que liberase verdaderamente la memoria debería liberarse también de la piscina.

¿Cómo empezamos a usar Multiprocessing? (iv)


import time
import random
import multiprocessing

class tarea:
    
    def __init__(self, cid):
        self.__cid=cid
        print("HIJO {0} - Nace".format(self.__cid))
    
    def __del__(self):
        print("HIJO {0} - Muere".format(self.__cid))
    
    def run(self):
        
        # Generamos un tiempo de espera aleatorio
        s=1+int(10*random.random())
        
        print("HIJO {0} - Inicio (Durmiendo {1} segundos)".format(self.__cid,s))
        time.sleep(s)
        print("HIJO {0} - Fin".format(self.__cid))

# Creamos la piscina (Pool)
piscina = []
for i in range(1,5):
    print("PADRE: creando HIJO {0}".format(i))
    piscina.append(multiprocessing.Process(name="Proceso {0}".format(i), target=tarea(i).run))

# Arrancamos a todos los hijos
print("PADRE: arrancando hijos")
for proceso in piscina:
    proceso.start()

print("PADRE: esperando a que los procesos hijos hagan su trabajo")
# Mientras la piscina tenga procesos
while piscina:
    # Para cada proceso de la piscina
    for proceso in piscina:
        # Revisamos si el proceso ha muerto
        if not proceso.is_alive():
            # Recuperamos el proceso y lo sacamos de la piscina
            proceso.join()
            piscina.remove(proceso)
            del(proceso)
    
    # Para no saturar, dormimos al padre durante 1 segundo
    print("PADRE: esperando a que los procesos hijos hagan su trabajo")
    time.sleep(1)

print("PADRE: todos los hijos han terminado, cierro")

proc_3.py

Colas (Queues)


from multiprocessing import Process, Queue

def f(q):
    q.put([42, None, 'hola'])

if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print q.get()
    p.join()

proc_4.py

Tuberías (Pipes)


from multiprocessing import Process, Pipe

def f(conn):
    conn.send([42, None, 'hola'])
    conn.close()

if __name__ == '__main__':
    conexion_padre, conexion_hijo = Pipe()
    p = Process(target=f, args=(conexion_hijo,))
    p.start()
    print conexion_padre.recv()
    p.join()

proc_5.py

Semáforos (Locks)


from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print 'hola mundo', i
    l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

proc_5.py

Ejemplo

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from multiprocessing import Process, Queue
from urllib2 import urlopen, URLError
from Queue import Empty

# Carácteres alfanuméricos.
NAME_VALID_CHARS = [chr(i) for i in range(48, 58) + range(97, 123)]

def chars_filter(s):
    """Remover carácteres inválidos."""
    return "".join(
        [c if c in NAME_VALID_CHARS else "" for c in s.lower()]
    )

def download_page_content(url):
    print "Descargando %s..." % url
    try:
        r = urlopen(url)
    except URLError as e:
        print "Error al acceder a %s." % url
        print e
    else:
        filename = chars_filter(url.lower()) + ".html"
        try:
            f = open(filename, "w")
        except IOError as e:
            print "Error al abrir %s." % filename
            print e
        else:
            f.write(r.read())
            f.close()
            r.close()

def worker(queue):
    """
    Toma un ítem de la cola y descarga su contenido,
    hasta que la misma se encuentre vacía.
    """
    while True:
        try:
            url = queue.get_nowait()
        except Empty:
            break
        else:
            download_page_content(url)

def main():
    urls = (
        "http://python.org/",
        "http://perl.org/",
        "http://ruby-lang.org/",
        "http://rust-lang.org/",
        "http://php.net/",
        "http://stackless.com/",
        "http://pypy.org/",
        "http://jython.org/",
        "http://ironpython.net/"
    )
    
    queue = Queue(9)
    for url in urls:
        queue.put(url)
    
    processes = []
    for i in range(3):
        processes.append(Process(target=worker, args=(queue,)))
        processes[i].start()
        print "Proceso %d lanzado." % (i + 1)
    
    for process in processes:
        process.join()
    
    print u"La ejecución a concluído."

if __name__ == "__main__":
    main()

proc_8.py

Asincronía

proc_8.py

  • Event loop + callbacks (Twisted)
  • Cooperative preemption usando corutinas (gevent)
  • AsyncIO

Recursos

  • http://www.juanmitaboada.com/multiprocessing-python/
    http://recursospython.com/guias-y-manuales/multiprocessing-tareas-concurrentes-con-procesos/

  • https://blog.aitorciki.net/2012/04/27/concurrencia-en-python/

     

UNAB: LP13

By Miguel Cantillana

UNAB: LP13

Clase 13

  • 708