2016-11-06 92 views
6

我使用的多道庫產卵兩個子進程。我想確保只要父進程還活着,如果子進程死了(接收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() 

如果我發送SIGKILL到counterProcess,它會正常重新啓動。但是,向helloProcess發送SIGKILL也會重新啓動counterProcess而不是helloProcess?

如果我發送一個SIGTERM父進程,父將退出,但子進程成爲孤兒和繼續。我如何糾正這種行爲?

回答

1

signal.SIGCHLD處理程序重新死去的孩子,母親必須調用os.wait功能之一,因爲Process.is_alive不在這裏工作了。
儘管可能,但它很複雜,因爲signal.SIGCHLD被送到母親,當它的一個孩子狀態改變f.e. signal.SIGSTOP,signal.SIGCONT或任何其他終止信號由孩子接收。
所以signal.SIGCHLD處理程序必須區分孩子的這些狀態。僅僅在signal.SIGCHLD交付時重新創建孩子可能會創造出超過必要的孩子。

下面的代碼使用os.waitpidos.WNOHANG使其無阻塞os.WUNTRACEDos.WCONTINUED學習如果signal.SIGCHLDsignal.SIGSTOPsignal.SIGCONT
os.waitpid不起作用,即如果Process實例中的任何一個爲print ed,即返回(0, 0),即在調用os.waitpid之前返回str(Process())

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的情況下重新創建死亡孩子。所以它比前者更簡單
創建了兩個孩子之後,母親進程爲SIGINT,SIGTERM,SIGQUIT設置了名爲term_child的信號處理程序。 term_child在調用時終止並加入每個孩子。

母親進程持續檢查孩子是否還活着,並在while循環中重新創建它們(如有必要)。

因爲每個孩子繼承了母親的信號處理器,該處理器SIGINT應重置爲Process.terminate它的默認值工作

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

沒有與代碼的幾個問題,所以我要去了他們sequentailly。

如果我向counterProcess發送一個SIGKILL,它將正確重啓。但是,向helloProcess發送SIGKILL也會重新啓動counterProcess而不是helloProcess?

由於multiprocessing.active_children()實際上並不是一個行爲,所以這種特殊行爲很可能是由於在主進程中沒有阻塞呼叫。我無法真正解釋程序行爲的確切原因,但在__main__函數中添加了阻塞調用,例如。

while True: 
    time.sleep(1) 

解決了這個問題。

另一個相當嚴重的問題是你傳遞對象到處理方式:

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

這是obsolate,考慮創建新的對象中:

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) 

最後,你可能想壓制任何信號從你的子進程來終止他們之前,否則他們會再次觸發回調:

signal(SIGCHLD, SIG_IGN) 

請注意,您馬雲想要的重新設計應用程序的體系結構,並利用一些multiprocessing提供的功能。

最終代碼:

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'不能很好地工作一起。在'signal.SIGCHLD'處理程序中,即使在子結束之後,Process.is_alive也返回True。 –