web-dev-qa-db-ja.com

ipython / jupyterノートブックでセルを実行するための新しいスレッド

1つのセルを実行するのに時間がかかる場合があります。実行中に、同じコンテキストで変数にアクセスし、同じノートブックで他のセルを作成して実行したいと思います。

セルに追加されたときにセルを実行すると自動的に新しいスレッドが作成され、ノートブックの共有グローバルデータで実行されるように使用できるipythonマジックはありますか?

28
chentingpc

それは答えではないかもしれませんが、むしろそれへの方向性です。そんなものは見たことがありませんが、やはり興味があります。

私の現在の調査結果は、それを定義する必要があることを示唆しています独自のカスタムセルマジック。ドキュメントのカスタムセルマジックセクションと私が検討する2つの例が適切なリファレンスです。

どちらのリンクも、コードをスレッドにラップします。それが出発点になるかもしれません。

UPDATE: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()

UPDATE 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()
11
kpykc

ここに私が思いついた小さなスニペットがあります

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が仕様上この機能を持たないのはそのためだと思います。スレッドがsleepまたはreadのようなシステムコールを実行している場合、例外は無視されます。

  3. スレッドを使用します。 PythonにはGILがあるため、%jobはpythonバイトコードを取り込むいくつかの重い計算には使用できません

5
alexanderkuk