Подтвердить что ты не робот

Новый поток для запуска ячейки в ipython/jupyter notebook

Иногда для запуска отдельной ячейки требуется много времени, хотя она работает, я хотел бы писать и запускать другие ячейки в одном ноутбуке, обращаясь к переменным в том же контексте.

Есть ли какая-либо магия ipython, которая может быть использована так, что когда она добавляется в ячейку, запуск ячейки будет автоматически создавать новый поток и запускаться с общими глобальными данными в записной книжке?

4b9b3361

Ответ 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