Многопроцессорная библиотека состояния зомби python3

Мой вопрос касается замены функции join(), чтобы избежать состояния несуществующего или зомби уже завершенных процессов при использовании многопроцессорной библиотеки python3. Есть ли альтернатива, которая может приостановить завершение дочерних процессов до тех пор, пока они не получат зеленый свет от основного процесса? Это позволяет им правильно завершать работу, не переходя в состояние зомби?

Я подготовил быструю иллюстрацию, используя следующий код, который запускает 20 различных процессов, первый процесс занимает 10 секунд загрузки, а все остальные — 3 секунды загрузки:

import os
import sys
import time
import multiprocessing as mp
from multiprocessing import Process

def exe(i):
    print(i)    
    if i == 1:
        time.sleep(10)
    else:
        time.sleep(3)
procs = []
for i in range(1,20):
    proc = Process(target=exe, args=(i,))
    proc.start()
    procs.append(proc)

for proc in procs:
    print(proc) # <-- I'm blocked to join others till the first process finishes its work load
    proc.join()

print("finished")

Если вы запустите сценарий, вы увидите, что все остальные процессы переходят в состояние зомби до тех пор, пока функция join() не будет освобождена из первого процесса. Это может сделать систему нестабильной или перегруженной!

Спасибо


person Ouali Abdelkader    schedule 13.11.2018    source источник
comment
Зомби требуют меньше ресурсов, чем приостановленный, но не завершенный процесс. Приостанавливать процесс вместо того, чтобы позволить ему стать зомби, совершенно контрпродуктивно.   -  person user2357112 supports Monica    schedule 14.11.2018
comment
Если вы приостановите процесс после того, как он закончит свою работу, никакие ресурсы не будут заняты, по крайней мере, на стороне программиста. Мое намерение состояло в том, чтобы сценарий имел полный контроль над процессами, не позволяя ОС контролировать мой код своими предположениями, особенно о том, нужно ли убивать процесс или нет!   -  person Ouali Abdelkader    schedule 14.11.2018


Ответы (1)


Согласно этой ветке, Марко Раухамаа пишет:

Если вам все равно, когда завершатся дочерние процессы, вы можете просто проигнорировать сигнал SIGCHLD:

import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)

Это предотвратит появление зомби.

На wait(2) справочной странице поясняется:

В POSIX.1-2001 указано, что если для SIGCHLD задано значение SIG_IGN или установлен флаг SA_NOCLDWAIT для SIGCHLD (см. ) будет блокироваться до тех пор, пока не будут завершены все дочерние процессы, а затем завершится сбоем, когда для errno будет установлено значение ECHILD. (Исходный стандарт POSIX оставил поведение при установке SIGCHLD в SIG_IGN неопределенным. Обратите внимание, что хотя по умолчанию для SIGCHLD установлено значение «игнорировать», явная установка для SIG_IGN приводит к другому обращению с дочерними процессами-зомби.)

Linux 2.6 соответствует требованиям POSIX. Однако в Linux 2.4 (и более ранних версиях) этого не происходит: если вызов wait() или waitpid() выполняется, когда SIGCHLD игнорируется, вызов ведет себя так же, как если бы SIGCHLD не был проигнорирован, то есть вызов блокируется до следующего дочерний процесс завершается, а затем возвращает идентификатор процесса и статус этого дочернего процесса.

Поэтому, если вы используете Linux 2.6 или POSIX-совместимую ОС, использование приведенного выше кода позволит завершить дочерние процессы, не становясь зомби. Если вы не используете POSIX-совместимую ОС, то вышеприведенная ветка предлагает несколько вариантов. Ниже приведена одна альтернатива, несколько похожая на третье предложение Марко Раухамаа.


Если по какой-то причине вам нужно знать, когда дочерние процессы завершаются, и вы хотите обрабатывать (по крайней мере, некоторые из них) по-другому, вы можете настроить очередь, чтобы дочерние процессы могли сигнализировать основному процессу, когда они завершены. Затем основной процесс может вызвать соответствующее соединение в том порядке, в котором он получает элементы из очереди:

import time
import multiprocessing as mp

def exe(i, q):
    try:
        print(i)    
        if i == 1:
            time.sleep(10)
        elif i == 10:
            raise Exception('I quit')
        else:
            time.sleep(3)
    finally:
        q.put(mp.current_process().name)

if __name__ == '__main__':
    procs = dict()
    q = mp.Queue()
    for i in range(1,20):
        proc = mp.Process(target=exe, args=(i, q))
        proc.start()
        procs[proc.name] = proc

    while procs:
        name = q.get()
        proc = procs[name]
        print(proc) 
        proc.join()
        del procs[name]

    print("finished")

дает результат, подобный

...    
<Process(Process-10, stopped[1])>  # <-- process with exception still gets joined
19
<Process(Process-2, started)>
<Process(Process-4, stopped)>
<Process(Process-6, started)>
<Process(Process-5, stopped)>
<Process(Process-3, stopped)>
<Process(Process-9, started)>
<Process(Process-7, stopped)>
<Process(Process-8, started)>
<Process(Process-13, started)>
<Process(Process-12, stopped)>
<Process(Process-11, stopped)>
<Process(Process-16, started)>
<Process(Process-15, stopped)>
<Process(Process-17, stopped)>
<Process(Process-14, stopped)>
<Process(Process-18, started)>
<Process(Process-19, stopped)>
<Process(Process-1, started)>      # <-- Process-1 ends last
finished
person unutbu    schedule 13.11.2018
comment
Спасибо, я нашел подход с очередью действительно интересным, он добавляет некоторую нагрузку при использовании очереди, но продолжайте думать более последовательно. - person Ouali Abdelkader; 14.11.2018