web-dev-qa-db-ja.com

Pythonのマルチプロセッシングを使用してプロセスを終了する方法

ハングしたり、制御できない問題がある他のいくつかのシステムに対して実行する必要のあるコードがあります。私はPythonのマルチプロセッシングを使用して子プロセスを生成し、メインプログラムとは独立して実行し、ハングしたり問題が発生したときに終了したいと思いますが、これを行う最善の方法はわかりません。

Terminateが呼び出されると、子プロセスは強制終了されますが、プロセスオブジェクトがなくなるまで解放されない無効なゾンビになります。ループが終了することのない以下のサンプルコードは、再度呼び出されたときにループを強制終了して再スポーンを許可するように機能しますが、これを実行する良い方法とは思えません(つまり、__ init __()ではmultiprocessing.Process()の方が良いでしょう)。

誰にも提案がありますか?

class Process(object):
    def __init__(self):
        self.thing = Thing()
        self.running_flag = multiprocessing.Value("i", 1)

    def run(self):
        self.process = multiprocessing.Process(target=self.thing.worker, args=(self.running_flag,))
        self.process.start()
        print self.process.pid

    def pause_resume(self):
        self.running_flag.value = not self.running_flag.value

    def terminate(self):
        self.process.terminate()

class Thing(object):
    def __init__(self):
        self.count = 1

    def worker(self,running_flag):
        while True:
            if running_flag.value:
                self.do_work()

    def do_work(self):
        print "working {0} ...".format(self.count)
        self.count += 1
        time.sleep(1)
15
Dan Littlejohn

子プロセスをバックグラウンドでデーモンとして実行できます。

_process.daemon = True
_

デーモンプロセスでエラーやハング(または無限ループ)が発生しても、メインプロセスには影響せず、メインプロセスが終了した後にのみ終了します。

これは、明示的な制御なしに親プロセスからメモリを取得し続ける多くの子デーモンプロセスに遭遇するまで、単純な問題に対して機能します。

最良の方法は、Queueを設定して、すべての子プロセスが親プロセスと通信できるようにして、それらをjoinして、きれいにクリーンアップできるようにすることです。以下に、子処理がハングしているかどうかを確認する簡単なコード(別名time.sleep(1000))を示し、メインプロセスがアクションを実行するためにメッセージをキューに送信します。

_import multiprocessing as mp
import time
import queue

running_flag = mp.Value("i", 1)

def worker(running_flag, q):
    count = 1
    while True:
        if running_flag.value:
            print "working {0} ...".format(count)
            count += 1
            q.put(count)
            time.sleep(1)
            if count > 3:
                # Simulate hanging with sleep
                print "hanging..."
                time.sleep(1000)

def watchdog(q):
    """
    This check the queue for updates and send a signal to it
    when the child process isn't sending anything for too long
    """
    while True:
        try:
            msg = q.get(timeout=10.0)
        except queue.Empty as e:
            print "[WATCHDOG]: Maybe WORKER is slacking"
            q.put("KILL WORKER")

def main():
    """The main process"""
    q = mp.Queue()

    workr = mp.Process(target=worker, args=(running_flag, q))
    wdog = mp.Process(target=watchdog, args=(q,))

    # run the watchdog as daemon so it terminates with the main process
    wdog.daemon = True

    workr.start()
    print "[MAIN]: starting process P1"
    wdog.start()

    # Poll the queue
    while True:
        msg = q.get()
        if msg == "KILL WATCHDOG":
            print "[MAIN]: Terminating slacking WORKER"
            workr.terminate()
            time.sleep(0.1)
            if not workr.is_alive():
                print "[MAIN]: WORKER is a goner"
                workr.join(timeout=1.0)
                print "[MAIN]: Joined WORKER successfully!"
                q.close()
                break # watchdog process daemon gets terminated

if __name__ == '__main__':
    main()
_

workerを終了しないと、workerが終了しないため、join()をメインプロセスにしようとすると、永久にブロックされます。

8
Pie 'Oh' Pah

Pythonマルチプロセッシングがプロセスを処理する方法は少し混乱しています。

マルチプロセッシングのガイドラインから:

ゾンビプロセスに参加する

Unixでは、プロセスが終了しても結合されていない場合、ゾンビになります。新しいプロセスが開始されるたびに(またはactive_children()が呼び出されるたびに)、まだ参加していないすべての完了したプロセスが参加するため、非常に多くなることはありません。また、終了したプロセスのProcess.is_aliveを呼び出すと、プロセスに参加します。それでも、開始するすべてのプロセスを明示的に結合することをお勧めします。

プロセスがゾンビにならないようにするには、一度プロセスを終了したら、そのjoin()メソッドを呼び出す必要があります。

システムでハングした呼び出しを処理するより簡単な方法が必要な場合は、 pebble をご覧ください。

6
noxdafox

(コメントするのに十分な評判ポイントがありません、これにより完全な回答)

@PieOhPah:この非常に素晴らしい例に感謝します。
残念ながら、ウォッチドッグが労働者を殺さないようにする小さな欠陥が1つだけあります。

if msg == "KILL WATCHDOG":

そのはず:

if msg == "KILL WORKER":

したがって、コードは次のようになります(python3用に更新された印刷):

import multiprocessing as mp
import time
import queue

running_flag = mp.Value("i", 1)

def worker(running_flag, q):
    count = 1
    while True:
        if running_flag.value:
            print ("working {0} ...".format(count))
            count += 1
            q.put(count)
            time.sleep(1)
            if count > 3:
                # Simulate hanging with sleep
                print ("hanging...")
                time.sleep(1000)

def watchdog(q):
    """
    This check the queue for updates and send a signal to it
    when the child process isn't sending anything for too long
    """
    while True:
        try:
            msg = q.get(timeout=10.0)
        except queue.Empty as e:
            print ("[WATCHDOG]: Maybe WORKER is slacking")
            q.put("KILL WORKER")

def main():
    """The main process"""
    q = mp.Queue()

    workr = mp.Process(target=worker, args=(running_flag, q))
    wdog = mp.Process(target=watchdog, args=(q,))

    # run the watchdog as daemon so it terminates with the main process
    wdog.daemon = True

    workr.start()
    print ("[MAIN]: starting process P1")
    wdog.start()

    # Poll the queue
    while True:
        msg = q.get()
#        if msg == "KILL WATCHDOG":
        if msg == "KILL WORKER":
            print ("[MAIN]: Terminating slacking WORKER")
            workr.terminate()
            time.sleep(0.1)
            if not workr.is_alive():
                print ("[MAIN]: WORKER is a goner")
                workr.join(timeout=1.0)
                print ("[MAIN]: Joined WORKER successfully!")
                q.close()
                break # watchdog process daemon gets terminated

if __name__ == '__main__':
    main()
3
Leo