2016-11-06 16 views
6

Tôi đang sử dụng thư viện đa xử lý để sinh ra hai tiến trình con. Tôi muốn đảm bảo rằng miễn là quá trình cha mẹ còn sống, nếu quá trình con chết (nhận được SIGKILL hoặc SIGTERM), chúng sẽ được khởi động lại tự động. Mặt khác, nếu tiến trình cha nhận được một SIGTERM/SIGINT, tôi muốn nó chấm dứt tất cả các tiến trình con rồi thoát ra.Đa xử lý Python - Chụp tín hiệu để khởi động lại quá trình con hoặc tắt quy trình cha

Đây là cách tôi tiếp cận vấn đề:

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

Nếu tôi gửi một SIGKILL đến counterProcess, nó sẽ khởi động lại một cách chính xác. Tuy nhiên, việc gửi SIGKILL đến helloProcess cũng khởi động lại bộ đếm ngược thay vì helloProcess?

Nếu tôi gửi SIGTERM đến tiến trình cha mẹ, cha mẹ sẽ thoát, nhưng quá trình con trở thành trẻ mồ côi và tiếp tục. Tôi làm cách nào để khắc phục hành vi này?

Trả lời

1

Để tạo trẻ chết từ signal.SIGCHLD xử lý, người mẹ phải gọi một trong os.wait chức năng, vì Process.is_alive không làm việc ở đây.
Mặc dù có thể, nó phức tạp, bởi vì signal.SIGCHLD được gửi cho mẹ khi một trong các trạng thái con của nó thay đổi f.e. signal.SIGSTOP, signal.SIGCONT hoặc bất kỳ tín hiệu chấm dứt nào khác được nhận bởi đứa trẻ.
Vì vậy, trình xử lý signal.SIGCHLD phải phân biệt giữa các trạng thái đề tài của trẻ. Chỉ đơn giản là tái tạo trẻ em khi giao hàng signal.SIGCHLD có thể tạo ra nhiều trẻ em hơn mức cần thiết.

Các mã sau sử dụng os.waitpid với os.WNOHANG để làm cho nó non-blocking và os.WUNTRACEDos.WCONTINUED cho việc học nếu signal.SIGCHLD là từ signal.SIGSTOP hoặc signal.SIGCONT.
os.waitpid không hoạt động, tức là trả về (0, 0) nếu bất kỳ trường hợp nào trong số Processprint ed, tức là str(Process()) trước khi bạn gọi os.waitpid.

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

Mã sau đây tái tạo trẻ em đã chết mà không sử dụng signal.SIGCHLD. Vì vậy, nó là đơn giản hơn so với trước đây.
Sau khi tạo hai con, quá trình mẹ đặt bộ xử lý tín hiệu có tên term_child cho SIGINT, SIGTERM, SIGQUIT. term_child chấm dứt và tham gia mỗi đứa trẻ khi được triệu tập.

Quy trình mẹ tiếp tục kiểm tra xem trẻ em có còn sống hay không và tái tạo chúng nếu cần thiết trong vòng lặp while.

Bởi vì mỗi đứa trẻ được thừa hưởng bộ xử lý tín hiệu từ mẹ, xử lý SIGINT nên được thiết lập lại giá trị mặc định cho Process.terminate làm việc

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

Có một số vấn đề với mã, vì vậy tôi sẽ xem xét chúng một cách liên tục.

Nếu tôi gửi SIGKILL tới bộ đếm ngược, nó sẽ khởi động lại chính xác. Tuy nhiên, việc gửi SIGKILL đến helloProcess cũng khởi động lại bộ đếm ngược thay vì helloProcess?

Hành vi đặc biệt này rất có thể do thiếu cuộc gọi chặn trong quy trình chính của bạn, vì multiprocessing.active_children() không thực sự hoạt động như một. Tôi thực sự không thể giải thích lý do chính xác tại sao chương trình hoạt động theo cách của nó, nhưng thêm chặn cuộc gọi trong chức năng __main__, ví dụ như.

while True: 
    time.sleep(1) 

giải quyết vấn đề.

Một vấn đề khá nghiêm trọng là cách bạn vượt qua các đối tượng vào handler:

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

đó là obsolate, cân nhắc bạn tạo các đối tượng mới bên:

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

    helloProcess = HelloWorld() 
    helloProcess.start() 

Lưu ý rằng cả hai đối tượng sử dụng bí danh khác nhau cho HelloWorld() đối tượng. Đối tượng một phần bị ràng buộc với bí danh trong hàm __main__, trong khi đối tượng trong gọi lại bị ràng buộc với bí danh phạm vi cục bộ của nó. Do đó, bằng cách gán đối tượng mới cho bí danh phạm vi cục bộ, bạn không thực sự ảnh hưởng đến đối tượng gọi lại bị ràng buộc (đối tượng đó vẫn bị ràng buộc đối tượng được tạo trong phạm vi __main__).

Bạn có thể sửa chữa nó bằng cách rebinding gọi lại tín hiệu của bạn với các đối tượng mới cùng cách trong phạm vi callback:

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

Tuy nhiên, điều này dẫn đến cái bẫy khác, vì bây giờ mọi quá trình con sẽ kế thừa gọi lại từ cha mẹ và truy cập nó mỗi lần nó nhận được tín hiệu.Để khắc phục điều đó, bạn có thể tạm thời thiết lập xử lý tín hiệu để mặc định ngay trước khi tạo child process:

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

Cuối cùng, bạn có thể muốn đè bẹp bất kỳ tín hiệu phát ra từ các quá trình con bạn trước khi chấm dứt họ, nếu không họ sẽ kích hoạt callback một lần nữa:

signal(SIGCHLD, SIG_IGN) 

Lưu ý rằng bạn muốn thiết kế lại kiến ​​trúc ứng dụng của mình và sử dụng một số tính năng multiprocessing cung cấp.

mã cuối cùng:

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' handler và' multiprocessing.Process' không hoạt động tốt cùng với nhau. Trong trình xử lý 'signal.SIGCHLD',' Process.is_alive' trả về 'True' ngay cả sau khi con chấm dứt. –

Các vấn đề liên quan