在ipython / jupyter笔记本中运行单元格的新线程

时间:2015-08-18 20:21:19

标签: multithreading ipython ipython-notebook jupyter

有时运行单个单元需要很长时间,而它正在运行,我想在同一个笔记本中编写并运行其他单元,在同一个上下文中访问变量。

是否有可以使用的ipython魔法,当它被添加到单元格时,运行单元格会自动创建一个新线程并在笔记本中使用共享的全局数据运行?

2 个答案:

答案 0 :(得分:10)

这可能不是一个答案,而是它的方向。我没有看到类似的东西,我也对此感兴趣。

我目前的调查结果表明,需要定义它自定义单元格魔法。好的参考资料将是文档中的自定义单元格魔术部分以及我将考虑的两个示例:

这些链接将代码包装在一个线程中。这可能是一个起点。

更新: github上的ngcm-tutorial描述了后台作业类

##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()

答案 1 :(得分:4)

这是我提出的一个小片段

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

此代码存在一些明显问题:

  1. 您无法在%job中使用任意代码。例如,没有分配而不是打印。所以我将它用于将结果存储在硬盘上的例程

  2. 有时kill_thread中的脏黑客不起作用。我认为这就是为什么IPython.lib.backgroundjobs没有设计这个功能的原因。如果线程正在执行某些系统调用,例如sleepread,则会忽略异常。

  3. 它使用线程。 Python有GIL,所以%job不能用于一些带有python字节码的繁重计算