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
- 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.
- 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