Если вы используете subprocess.Popen()
для создания дочерних процессов и не хотите, чтобы они были уничтожены сигналом SIGINT, используйте аргумент preexec_fn
, чтобы установить расположение сигнала SIGINT на игнорируемое до выполнения нового двоичного файла:
child = subprocess.Popen(...,
preexec_fn = lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
где ...
- заполнитель для ваших текущих параметров.
Если вы используете фактические потоки (либо потоки, либо модуль потоков), сигнальный модуль Python настраивает все так, чтобы только основной/ Первоначальный поток может принимать сигналы или устанавливать обработчики сигналов.Таким образом, на правильные потоки не влияют сигналы в Python.
В случае subprocess.Popen()
дочерний процесс первоначально наследует копию процесса, включая обработчики сигналов.Это означает, что есть небольшое окно, во время которого дочерний процесс может перехватить сигнал, используя тот же код, что и родительский процесс;но, поскольку это отдельный процесс, видны только его побочные эффекты.(Например, если обработчик сигнала вызывает sys.exit()
, выйдет только дочерний процесс. Обработчик сигнала в дочернем процессе не может изменить какие-либо переменные в родительском процессе.)
Чтобы избежать этого, родительский процессможет временно переключиться на другой обработчик сигнала, который запоминает, только если сигнал перехвачен, на время создания подпроцесса:
import signal
# Global variables for sigint diversion
sigint_diverted = False # True if caught while diverted
sigint_original = None # Original signal handler
def sigint_divert_handler():
global sigint_diverted
sigint_diverted = True
def sigint_divert(interrupts=False):
"""Temporarily postpone SIGINT signal delivery."""
global sigint_diverted
global sigint_original
sigint_diverted = False
sigint_original = signal.signal(signal.SIGINT, sigint_divert_handler)
signal.siginterrupt(signal.SIGINT, interrupts)
def sigint_restore(interrupts=True):
"""Restore SIGINT signal delivery to original handler."""
global sigint_diverted
global sigint_original
original = sigint_original
sigint_original = None
if original is not None:
signal.signal(signal.SIGINT, original)
signal.siginterrupt(signal.SIGINT, interrupts)
diverted = sigint_diverted
sigint_diverted = False
if diverted and original is not None:
original(signal.SIGINT)
С указанными выше помощниками идея заключается в том, что перед созданием дочернего процесса (используя модуль подпроцесса или некоторые функции модуля os), вы вызываете sigint_divert()
.Дочерний процесс наследует копию перенаправленного обработчика SIGINT.После создания дочернего процесса вы восстанавливаете обработку SIGINT, вызывая sigint_restore()
.(Обратите внимание, что если вы вызвали signal.siginterrupt(signal.SIGINT, False)
после установки исходного обработчика SIGINT, чтобы его доставка не вызывала исключений IOError, вам следует вместо этого вызвать sigint_restore(False)
.)
Таким образом, обработчик сигнала вдочерний процесс является обработчиком перенаправленного сигнала, который устанавливает только глобальный флаг и больше ничего не делает.Конечно, вы все равно хотите использовать параметр preexec_fn =
для subprocess.Popen()
, чтобы сигнал SIGINT полностью игнорировался при выполнении фактического двоичного файла в дочернем процессе.
sigint_restore()
не только восстанавливаеторигинальный обработчик сигнала, но если обработчик перенаправленного сигнала поймал сигнал SIGINT, он «повторно вызывается» путем непосредственного вызова исходного обработчика сигнала.Это предполагает, что оригинальный обработчик уже установлен;в противном случае вы можете использовать os.kill(os.getpid(), signal.SIGKILL)
.
Python 3.3 и более поздних версий в операционных системах, отличных от Windows, предоставляет маски сигналов , которые можно использовать для «блокировки» сигналов на время,Блокировка означает, что доставка сигнала откладывается до разблокирования;не игнорируетсяЭто именно то, что пытается выполнить вышеуказанный код переадресации сигналов.
Сигналы не ставятся в очередь, поэтому, если один сигнал уже находится в состоянии ожидания, любые другие сигналы того же типа игнорируются.(Таким образом, только один сигнал каждого типа, скажем, SIGINT, может одновременно находиться в состоянии ожидания.)
Это позволяет шаблону использовать две вспомогательные функции,
def block_signals(sigset = { signal.SIGINT }):
mask = signal.pthread_sigmask(signal.SIG_BLOCK, {})
signal.pthread_sigmask(signal.SIG_BLOCK, sigset)
return mask
def restore_signals(mask):
signal.pthread_sigmask(signal.SIG_SETMASK, mask)
, чтобы один вызывалmask = block_signals()
до создания потока или подпроцесса и restore_signals(mask)
после.В созданном потоке или подпроцессе сигнал SIGINT по умолчанию блокируется.
Заблокированный сигнал SIGINT также можно использовать с помощью signal.sigwait({signal.SIGINT})
(который блокируется до тех пор, пока один не доставлен) или signal.sigtimedwait({signal.SIGINT}, 0)
, который немедленно возвращается ссигнал, если он находится в режиме ожидания, и None
в противном случае.
Когда подпроцесс управляет своей собственной маской сигналов и обработчиками сигналов, мы не можем заставить его игнорировать сигнал SIGINT.
В Unix/ POSIXy-машины, мы можем остановить отправку SIGINT дочернему процессу, однако, отсоединив дочерний процесс от управляющего терминала и запустив его в своем собственном сеансе.
Необходимо два набора измененийв subprocess.Popen()
:
Выполните команду или двоичный файл в setsid
: либо [ "setsid", "program", args.. ]
, либо "setsid sh -c 'command'"
, в зависимости от того, предоставляете ли вы двоичный файл для выполнения в виде списка илив виде строки.
setsid
- утилита командной строки, которая запускает указанную программу с указанными аргументами в новом сеансе.Новый сеанс не имеет управляющего терминала, что означает, что он не получит SIGINT, если пользователь нажмет Ctrl + C .
Если родительский объект не использует канал для подпроцесса 'stdin
, stdout
или stderr
, они должны быть явно открыты в os.devnull
:
stdin=open(os.devnull, 'rb')
,
stdout=open(os.devnull, 'wb')
,
stderr=open(os.devnull, 'wb')
Это гарантирует, что подпроцесс не попадет под управляющий терминал.(Именно управляющий терминал посылает сигнал SIGINT каждому процессу, когда пользователь нажимает Ctrl + C .)
Если родительский процессхочет, он может отправить сигнал SIGINT дочернему процессу, используя os.kill(child.pid, signal.SIGINT)
.