parallel-cell
parallel-cell copied to clipboard
Удобный способ организовать примитивные параллельный вычисления в Jupyter (IPython Notebook)
Иногда возникает желание запустить вычисления в ячейке и, не дожидаясь пока они закончатся, продолжить работу. Например, нужно скачать 1000 урлов и достать у них заголовки страниц. Хорошо бы запустить процесс скачивания и сразу начать отлаживать код для выделения заголовков.
Можно использовать такой код:
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
Использовать его можно так:

jobs
ведёт учёт фоновых операций:

Чтобы убить операцию нужно использовать специальный хак:
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')
Использовать так:

jobs
также копит стектрейсы (kill_thread
кидает SystemError
внутри треда):

%job
можно даже использовать как альтернативу multiprocessing.ThreadPool
:

Нарезать последовательность на заданное количество кусочков удобно функцией:
def get_chunks(sequence, count):
count = min(count, len(sequence))
chunks = [[] for _ in range(count)]
for index, item in enumerate(sequence):
chunks[index % count].append(item)
return chunks
Завершить работу пула можно так:

Что нужно понимать
-
%job
работает на тредах, поэтому нужно помнить про GIL.%job
— это не про распараллеливание тяжелых вычислений, которые происходят в питоновом байт-коде. Это про IO-bound операции и вызов внешних утилит. -
Нельзя нормально завершить произвольный код внутри треда, поэтому в
kill_thread
используется хак. Этот хак работает не всегда. Например, если код внутри треда выполняетsleep
исключение, которое кидаетkill_thread
игнорируется. -
Код в
%job
выполняется черезeval
. Грубо говоря, можно использовать выражения, который могут встречаться после знака=
. Никакихprint
и присваиваний. Впрочем, всегда можно завернуть сложный код в функцию и выполнить%job f()
-
Передача сообщений из %job выполняется через жёсткий диск. Например, нельзя непосредственно получить содержание скачанной страницы, нужно его сохранить на диск, а потом прочитать.