Новый поток для запуска ячейки в ipython/jupyter notebook
Иногда для запуска отдельной ячейки требуется много времени, хотя она работает, я хотел бы писать и запускать другие ячейки в одном ноутбуке, обращаясь к переменным в том же контексте.
Есть ли какая-либо магия ipython, которая может быть использована так, что когда она добавляется в ячейку, запуск ячейки будет автоматически создавать новый поток и запускаться с общими глобальными данными в записной книжке?
Ответы
Ответ 1
Это может быть не ответ, а направление. Я не видел ничего подобного, все равно меня тоже интересует.
Мои текущие результаты, предполагающие, что нужно определить собственную собственную магию клеток. Хорошие ссылки - это раздел пользовательской ячейки в документации и два примера, которые я бы рассмотрел:
Обе эти ссылки обертывают код в потоке. Это может быть отправной точкой.
ОБНОВЛЕНИЕ: ngcm-учебник в github содержит описание классов фоновых заданий
##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()
ОБНОВЛЕНИЕ 2: Другая опция:
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()
Ответ 2
Вот небольшой фрагмент, который я придумал
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
Он использует встроенный модуль IPython IPython.lib.backgroundjobs
. Таким образом, код мал и прост и никаких новых зависимостей не вводится.
Я использую его следующим образом:
jobs = jobs_manager()
%job [fetch_url(_) for _ in urls] # saves html file to disk
Starting job # 0 in a separate thread.
Затем вы можете контролировать состояние с помощью:
jobs.status()
Running jobs:
1 : [fetch_url(_) for _ in urls]
Dead jobs:
0 : [fetch_url(_) for _ in urls]
Если сбой задания, вы можете проверить трассировку стека с помощью
jobs.traceback(0)
Невозможно убить работу. Поэтому я тщательно использую этот грязный хак:
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')
Он поднимает SystemError
в заданном потоке. Поэтому, чтобы убить работу, я делаю
kill_thread(jobs.all[1])
Чтобы убить все выполняемые задания, я делаю
for thread in jobs.running:
kill_thread(thread)
Мне нравится использовать %job
с виджетным баром прогресса https://github.com/alexanderkuk/log-progress следующим образом:
%job [fetch_url(_) for _ in log_progress(urls, every=1)]
http://g.recordit.co/iZJsJm8BOL.gif
Можно использовать %job
вместо 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
Некоторые очевидные проблемы с этим кодом:
-
Вы не можете использовать произвольный код в %job
. Например, не может быть присвоений, а не отпечатков. Поэтому я использую его с подпрограммами, которые хранят результаты на жестком диске
-
Иногда грязный взлом в kill_thread
не работает. Я думаю, поэтому IPython.lib.backgroundjobs
не имеет этой функциональности по дизайну. Если поток выполняет какой-то системный вызов, например, sleep
или read
исключение игнорируется.
-
Он использует потоки. Python имеет GIL, поэтому %job
не может использоваться для некоторых тяжелых вычислений, которые принимают байт-код python