multithreading - type - jupyter latex
un nuevo hilo para ejecutar una celda en el portátil ipython/jupyter (2)
Aquí hay un pequeño fragmento que se me ocurrió
def jobs_manager():
from IPython.lib.backgroundjobs import BackgroundJobManager
from IPython.core.magic import register_line_magic
from IPython import get_ipython
jobs = BackgroundJobManager()
@register_line_magic
def job(line):
ip = get_ipython()
jobs.new(line, ip.user_global_ns)
return jobs
Utiliza el módulo IPython.lib.backgroundjobs
IPython IPython.lib.backgroundjobs
. Así que el código es pequeño y simple y no se introducen nuevas dependencias.
Lo uso así:
jobs = jobs_manager()
%job [fetch_url(_) for _ in urls] # saves html file to disk
Starting job # 0 in a separate thread.
Luego puedes monitorear el estado con:
jobs.status()
Running jobs:
1 : [fetch_url(_) for _ in urls]
Dead jobs:
0 : [fetch_url(_) for _ in urls]
Si el trabajo falla, puede inspeccionar el seguimiento de pila con
jobs.traceback(0)
No hay manera de matar un trabajo. Así que cuidadosamente uso este truco sucio:
def kill_thread(thread):
import ctypes
id = thread.ident
code = ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(id),
ctypes.py_object(SystemError)
)
if code == 0:
raise ValueError(''invalid thread id'')
elif code != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(id),
ctypes.c_long(0)
)
raise SystemError(''PyThreadState_SetAsyncExc failed'')
Plantea SystemError
en un hilo dado. Así que para matar un trabajo que hago
kill_thread(jobs.all[1])
Para matar a todos los trabajos en ejecución que hago
for thread in jobs.running:
kill_thread(thread)
Me gusta usar %job
con la barra de progreso basada en widgets https://github.com/alexanderkuk/log-progress como este:
%job [fetch_url(_) for _ in log_progress(urls, every=1)]
http://g.recordit.co/iZJsJm8BOL.gif
Incluso se puede usar %job
lugar de multiprocessing.TreadPool
. multiprocessing.TreadPool
:
for chunk in get_chunks(urls, 3):
%job [fetch_url(_) for _ in log_progress(chunk, every=1)]
http://g.recordit.co/oTVCwugZYk.gif
Algunos problemas obvios con este código:
No puede utilizar código arbitrario en
%job
. No puede haber asignaciones y no impresiones por ejemplo. Así que lo uso con rutinas que almacenan resultados en el disco duro.A veces, el truco sucio en
kill_thread
no funciona. Creo que es por eso queIPython.lib.backgroundjobs
no tiene esta funcionalidad por diseño. Si el subproceso está haciendo alguna llamada al sistema, como lasleep
o la excepción deread
se ignora.Utiliza hilos. Python tiene GIL, por lo que no se puede usar
%job
para algunos cálculos pesados que toman el código de byte de python
A veces lleva mucho tiempo ejecutar una sola celda, mientras se ejecuta, me gustaría escribir y ejecutar otras celdas en el mismo cuaderno, accediendo a las variables en el mismo contexto.
¿Existe alguna magia de ipython que pueda usarse de tal manera que cuando se agrega a una celda, la ejecución de la celda creará automáticamente un nuevo hilo y se ejecutará con datos globales compartidos en el cuaderno?
Puede que no sea una respuesta, sino la dirección de la misma. No vi nada de eso, todavía estoy interesado en esto también.
Mis hallazgos actuales sugieren que uno necesita definir su propia magia celular personalizada . Buenas referencias serían la sección personalizada de magia celular en la documentación y dos ejemplos que consideraría:
- memit: uso de memoria mágica para IPython https://gist.github.com/vene/3022718
- Ilustración de multiproceso Python vs multiproceso: http://nathangrigg.net/2015/04/python-threading-vs-processes/
Ambos enlaces envuelven el código en un hilo. Ese podría ser un punto de partida.
ACTUALIZACIÓN: ngcm-tutorial at github tiene una descripción de la clase de trabajos en segundo plano
##github.com/jupyter/ngcm-tutorial/blob/master/Day-1/IPython%20Kernel/Background%20Jobs.ipynb
from IPython.lib import backgroundjobs as bg
jobs = bg.BackgroundJobManager()
def printfunc(interval=1, reps=5):
for n in range(reps):
time.sleep(interval)
print(''In the background... %i'' % n)
sys.stdout.flush()
print(''All done!'')
sys.stdout.flush()
jobs.new(''printfunc(1,3)'')
jobs.status()
ACTUALIZACIÓN 2: Otra opción:
from IPython.display import display
from ipywidgets import IntProgress
import threading
class App(object):
def __init__(self, nloops=2000):
self.nloops = nloops
self.pb = IntProgress(description=''Thread loops'', min=0, max=self.nloops)
def start(self):
display(self.pb)
while self.pb.value < self.nloops:
self.pb.value += 1
self.pb.color = ''red''
app = App(nloops=20000)
t = threading.Thread(target=app.start)
t.start()
#t.join()