2016-11-06 11 views
6

マルチプロセスライブラリを使用して2つの子プロセスを生成しています。私は、親プロセスが生存している限り、子プロセスが死んだ場合(SIGKILLまたはSIGTERMを受け取った場合)、自動的に再起動されるようにしたいと思います。一方、親プロセスがSIGTERM/SIGINTを受け取った場合は、すべての子プロセスを終了して終了します。Pythonマルチプロセッシング - シグナルをキャプチャして子プロセスを再起動する、または親プロセスをシャットダウンする

これは、私が問題に近づい方法です:

import sys 
import time 
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN 
from functools import partial 
import multiprocessing 
import setproctitle 

class HelloWorld(multiprocessing.Process): 
    def __init__(self): 
     super(HelloWorld, self).__init__() 

     # ignore, let parent handle it 
     signal(SIGTERM, SIG_IGN) 

    def run(self): 

     setproctitle.setproctitle("helloProcess") 

     while True: 
      print "Hello World" 
      time.sleep(1) 

class Counter(multiprocessing.Process): 
    def __init__(self): 
     super(Counter, self).__init__() 

     self.counter = 1 

     # ignore, let parent handle it 
     signal(SIGTERM, SIG_IGN) 

    def run(self): 

     setproctitle.setproctitle("counterProcess") 

     while True: 
      print self.counter 
      time.sleep(1) 
      self.counter += 1 


def signal_handler(helloProcess, counterProcess, signum, frame): 

    print multiprocessing.active_children() 
    print "helloProcess: ", helloProcess 
    print "counterProcess: ", counterProcess 

    if signum == 17: 

     print "helloProcess: ", helloProcess.is_alive() 

     if not helloProcess.is_alive(): 
      print "Restarting helloProcess" 

      helloProcess = HelloWorld() 
      helloProcess.start() 

     print "counterProcess: ", counterProcess.is_alive() 

     if not counterProcess.is_alive(): 
      print "Restarting counterProcess" 

      counterProcess = Counter() 
      counterProcess.start() 

    else: 

     if helloProcess.is_alive(): 
      print "Stopping helloProcess" 
      helloProcess.terminate() 

     if counterProcess.is_alive(): 
      print "Stopping counterProcess" 
      counterProcess.terminate() 

     sys.exit(0) 



if __name__ == '__main__': 

    helloProcess = HelloWorld() 
    helloProcess.start() 

    counterProcess = Counter() 
    counterProcess.start() 

    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
     signal(signame, partial(signal_handler, helloProcess, counterProcess)) 

    multiprocessing.active_children() 

私はcounterProcessにSIGKILLを送信した場合、それが正しく再起動します。ただし、helloProcessにSIGKILLを送信すると、helloProcessではなくcounterProcessが再起動されます。

親プロセスにSIGTERMを送信すると、親プロセスは終了しますが、子プロセスは孤児になって処理を続行します。この動作を修正するにはどうすればよいですか?

答えて

1

signal.SIGCHLDハンドラから死んだ子供を再現するProcess.is_aliveはここで働いていないので、母は、os.wait関数のいずれかを呼び出す必要があります。
signal.SIGCHLDは子供のステータスが変更されたときに母親に配送されるため、可能ですが複雑です。 signal.SIGSTOP,signal.SIGCONTまたは他の任意の着信信号が子供によって受信される。
ハンドラは、子のこれらの状態を区別する必要があります。 signal.SIGCHLDが届いたときに子供を作り直すだけで、必要以上に子供が増えることがあります。

次のコードは、signal.SIGCHLDsignal.SIGSTOPまたはsignal.SIGCONTからであれば学習のために、それは非ブロックとos.WUNTRACED作るためにos.WNOHANGos.waitpidを使用し、os.WCONTINUED
os.waitpidは、os.waitpidに電話する前にのインスタンスがprintである場合、つまりstr(Process())の場合は(0, 0)を返します。

import sys 
import time 
from signal import signal, pause, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_DFL 
import multiprocessing 
import os 

class HelloWorld(multiprocessing.Process): 
    def run(self): 
     # reset SIGTERM to default for Process.terminate to work 
     signal(SIGTERM, SIG_DFL) 
     while True: 
      print "Hello World" 
      time.sleep(1) 

class Counter(multiprocessing.Process): 
    def __init__(self): 
     super(Counter, self).__init__() 
     self.counter = 1 

    def run(self): 
     # reset SIGTERM to default for Process.terminate to work 
     signal(SIGTERM, SIG_DFL) 
     while True: 
      print self.counter 
      time.sleep(1) 
      self.counter += 1 


def signal_handler(signum, _): 
    global helloProcess, counterProcess 

    if signum == SIGCHLD: 
     pid, status = os.waitpid(-1, os.WNOHANG|os.WUNTRACED|os.WCONTINUED) 
     if os.WIFCONTINUED(status) or os.WIFSTOPPED(status): 
      return 
     if os.WIFSIGNALED(status) or os.WIFEXITED(status): 
      if helloProcess.pid == pid: 
       print("Restarting helloProcess") 
       helloProcess = HelloWorld() 
       helloProcess.start() 

      elif counterProcess.pid == pid: 
       print("Restarting counterProcess") 
       counterProcess = Counter() 
       counterProcess.start() 

    else: 
     # mother shouldn't be notified when it terminates children 
     signal(SIGCHLD, SIG_DFL) 
     if helloProcess.is_alive(): 
      print("Stopping helloProcess") 
      helloProcess.terminate() 

     if counterProcess.is_alive(): 
      print("Stopping counterProcess") 
      counterProcess.terminate() 

     sys.exit(0) 

if __name__ == '__main__': 

    helloProcess = HelloWorld() 
    helloProcess.start() 

    counterProcess = Counter() 
    counterProcess.start() 

    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
     signal(signame, signal_handler) 

    while True: 
     pause() 

次のコードは、signal.SIGCHLDを使用しないで死んでいる子を再作成します。したがって、はより簡単ですより前です。
motherプロセスは、2つの子を作成した後、SIGINT、SIGTERM、SIGQUITに対してterm_childという名前のシグナルハンドラを設定します。 term_childは、呼び出し時に各子を終了して結合します。

マザープロセスは、子供が生存しているかどうかをチェックし続け、必要に応じてループのwhileにそれらを再作成します。

すべての子供は母親からのシグナルハンドラを継承するので、SIGINTハンドラは

import sys 
import time 
from signal import signal, SIGINT, SIGTERM, SIGQUIT 
import multiprocessing 

class HelloWorld(multiprocessing.Process):  
    def run(self): 
     signal(SIGTERM, SIG_DFL) 
     while True: 
      print "Hello World" 
      time.sleep(1) 

class Counter(multiprocessing.Process): 
    def __init__(self): 
     super(Counter, self).__init__() 
     self.counter = 1 

    def run(self): 
     signal(SIGTERM, SIG_DFL) 
     while True: 
      print self.counter 
      time.sleep(1) 
      self.counter += 1 

def term_child(_, __): 
    for child in children: 
     child.terminate() 
     child.join() 
    sys.exit(0) 

if __name__ == '__main__': 

    children = [HelloWorld(), Counter()] 
    for child in children: 
     child.start() 

    for signame in (SIGINT, SIGTERM, SIGQUIT): 
     signal(signame, term_child) 

    while True: 
     for i, child in enumerate(children): 
      if not child.is_alive(): 
       children[i] = type(child)() 
       children[i].start() 
     time.sleep(1) 
5

コードにはいくつかの問題がありますので、私はそれらを順番に調べます。

SIGKILLをcounterProcessに送信すると、正しく再起動されます。ただし、helloProcessにSIGKILLを送信すると、helloProcessではなくcounterProcessが再起動されます。

この特異な挙動はmultiprocessing.active_children()が本当に一つとして機能していないので、原因あなたのメインプロセスで通話を遮断するの不足のために最も可能性が高いです。私は実際にプログラムがそれと同じように動作する正確な理由を説明することはできませんが、例えば__main__機能でブロッキングコールを追加しています。

while True: 
    time.sleep(1) 

が問題を解決します。

もうかなり深刻な問題は、あなたがハンドラにオブジェクトを渡す方法です:obsolateある

helloProcess = HelloWorld() 
... 
partial(signal_handler, helloProcess, counterProcess) 

、あなたの中に新しいオブジェクトを作成考慮:両方のオブジェクトがために別のエイリアスを使用することを

if not helloProcess.is_alive(): 
    print "Restarting helloProcess" 

    helloProcess = HelloWorld() 
    helloProcess.start() 

注意HelloWorld()オブジェクトです。部分オブジェクトは__main__のエイリアスにバインドされ、コールバックのオブジェクトはローカルスコープエイリアスにバインドされます。したがって、ローカルスコープエイリアスに新しいオブジェクトを割り当てることで、コールバックがバインドされているオブジェクトには実際には影響しません(まだ__main__スコープで作成されたオブジェクトにバインドされます)。今、すべての子プロセスは親とアクセスからのコールバックを継承するため、

def signal_handler(...): 
    ... 
    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
     signal(signame, partial(signal_handler, helloProcess, counterProcess)) 
    ... 

しかし、これは別のトラップにつながる:

あなたは、新しいオブジェクトをコールバックスコープで同じように、あなたのシグナルコールバックを再結合することによってそれを修正することができますそれは信号を受信する度にそれを修正するには、一時的に右の子プロセスを作成する前にデフォルトにシグナルハンドラを設定することができます。

for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
    signal(signame, SIG_DFL) 

は最後に、あなたがそれらを終了する前に、あなたの子プロセスからの任意の信号をスケルチしたい場合があり、そうでなければ、再びコールバックをトリガします。あなたのアプリケーションのアーキテクチャを再設計し、multiprocessingが提供する機能のいくつかを利用したいミリアンペア

signal(SIGCHLD, SIG_IGN) 

注意。

決勝コード:

import sys 
import time 
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN, SIG_DFL 
from functools import partial 
import multiprocessing 
#import setproctitle 

class HelloWorld(multiprocessing.Process): 
    def __init__(self): 
     super(HelloWorld, self).__init__() 

     # ignore, let parent handle it 
     #signal(SIGTERM, SIG_IGN) 

    def run(self): 

     #setproctitle.setproctitle("helloProcess") 

     while True: 
      print "Hello World" 
      time.sleep(1) 

class Counter(multiprocessing.Process): 
    def __init__(self): 
     super(Counter, self).__init__() 

     self.counter = 1 

     # ignore, let parent handle it 
     #signal(SIGTERM, SIG_IGN) 

    def run(self): 

     #setproctitle.setproctitle("counterProcess") 

     while True: 
      print self.counter 
      time.sleep(1) 
      self.counter += 1 


def signal_handler(helloProcess, counterProcess, signum, frame): 

    print multiprocessing.active_children() 
    print "helloProcess: ", helloProcess 
    print "counterProcess: ", counterProcess 

    print "current_process: ", multiprocessing.current_process() 

    if signum == 17: 

     # Since each new child inherits current signal handler, 
     # temporarily set it to default before spawning new child. 
     for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
      signal(signame, SIG_DFL) 

     print "helloProcess: ", helloProcess.is_alive() 

     if not helloProcess.is_alive(): 
      print "Restarting helloProcess" 

      helloProcess = HelloWorld() 
      helloProcess.start() 

     print "counterProcess: ", counterProcess.is_alive() 

     if not counterProcess.is_alive(): 
      print "Restarting counterProcess" 

      counterProcess = Counter() 
      counterProcess.start() 

     # After new children are spawned, revert to old signal handling policy. 
     for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
      signal(signame, partial(signal_handler, helloProcess, counterProcess)) 


    else: 

     # Ignore any signal that child communicates before quit 
     signal(SIGCHLD, SIG_IGN) 

     if helloProcess.is_alive(): 
      print "Stopping helloProcess" 
      helloProcess.terminate() 

     if counterProcess.is_alive(): 
      print "Stopping counterProcess" 
      counterProcess.terminate() 

     sys.exit(0) 



if __name__ == '__main__': 

    helloProcess = HelloWorld() 
    helloProcess.start() 

    counterProcess = Counter() 
    counterProcess.start() 

    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]: 
     signal(signame, partial(signal_handler, helloProcess, counterProcess)) 

    while True: 
     print multiprocessing.active_children() 
     time.sleep(1) 
+0

'signal.SIGCHLD'ハンドラと' multiprocessing.Process'がうまく動作しない仕事にProcess.terminate、そのデフォルト値にリセットする必要があります一緒に。 'signal.SIGCHLD'ハンドラでは、' Process.is_alive'は子プロセスが終了した後でも 'True'を返します。 –

関連する問題