Сигналы, в отсутствие лучшей аналогии, можно сравнить с палкой, которой тыкают в процесс. Программы генерируют сигналы, чтобы запустить обработчик данного сигнала в другом процессе. Операционная система тоже этим занимается — некоторые сигналы, генерируемые при необычных системных событиях, могут завершить программу, если их не обработать. Если это несколько напоминает возбуждение исключений в Python, то это неслучайно: сигналы являются программно генерируемыми событиями и аналогом исключений, действующими между процессами. Однако, в отличие от исключений, сигналы идентифицируются по номеру, не помещаются в очередь и в действительности являются механизмом асинхронных событий за пределами интерпретатора Python, управляемым операционной системой.
Чтобы сигналы можно было использовать в сценариях, в состав стандартной библиотеки Python входит модуль signal, который позволяет программам на языке Python регистрировать функции в качестве обработчиков сигналов. Этот модуль доступен как в Unix-подобных системах, так и в Windows (хотя в версии для Windows число сигналов может оказаться меньше). Для иллюстрации базового интерфейса сигналов в примере 5.27 приводится сценарий, устанавливающий функцию обработчика сигнала, номер которого передается как аргумент командной строки.
Пример 5.27. PP4E\System\Processes\signal1.py
обработка сигналов в Python; номер сигнала N передается как аргумент командной строки; чтобы передать сигнал этому процессу, используйте команду оболочки “kill —N pid”; большинство обработчиков сигналов восстанавливаются интерпретатором после обработки сигнала (смотрите главу, посвященную сетевым сценариям, где приводится описание сигнала SIGCHLD); в Windows модуль signal также доступен, но он определяет небольшое количество типов сигналов, а кроме того, в Windows отсутствует функция os.kill;
import sys, signal, time
def now(): return time.ctime(time.time()) # строка с текущим временем
def onSignal(signum, stackframe): # обработчик сигнала
print(‘Got signal’, signum, ‘at’, now()) # большинство обработчиков
# остаются действующими
signum = int(sys.argv[1])
signal.signal(signum, onSignal) # установить обработчик сигнала
while True: signal.pause() # ждать сигнала (или: pass)
Здесь используются только две функции из модуля signal: signal.signal
Принимает номер сигнала, объект функции и устанавливает эту функцию в качестве обработчика сигнала с данным номером. Интерпретатор Python автоматически восстанавливает большинство обработчиков сигналов, когда они возникают, поэтому нет необходимости повторно вызывать эту функцию внутри самого обработчика сигнала, чтобы заново его зарегистрировать. То есть обработчики всех сигналов, за исключением сигнала SIGCHLD, остаются установленными, пока не будут сброшены явно (например, путем установки его в значение SIG_DFL, чтобы восстановить режим по умолчанию, или в значение SIG_IGN, чтобы игнорировать сигнал). Поведение сигнала SIGCHLD зависит от платформы.
signal.pause
Приостанавливает процесс, пока не будет перехвачен следующий сигнал. Функция time.sleep действует аналогично, но не работает с сигналами в моей системе Linux, — она генерирует ошибку прерванного системного вызова. Цикл while True: pass тоже остановит сценарий, но будет напрасно тратить ресурсы процессора.
Ниже приводится вывод сценария, выполняющегося под управлением Cygwin в Windows (точно так же он будет действовать в любой Unix— подобной системе, такой как Linux): номер ожидаемого сигнала (12) передается в командной строке, а программа запускается в фоновом режиме с помощью оператора оболочки & (доступного в большинстве Unix-подобных оболочек):
PID |
PPID |
PGID |
WINPID |
TTY |
UID |
STIME |
COMMAND |
8944 |
1 |
8944 |
8944 |
con |
1004 |
18:09:54 |
/usr/bin/bash |
8224 |
7336 |
8224 |
10020 |
con |
1004 |
18:26:47 |
/usr/local/bin/python |
8380 |
7336 |
8380 |
428 |
con |
1004 |
18:26:50 |
/usr/bin/ps |
[C:\…\PP4E\System\Processes]$ python signal1.py 12 & [1] 8224 |
$ kill -12 8224 Got signal 12 at Sun Mar 7 18:27:28 2010 |
$ kill -12 8224
Got signal 12 at Sun Mar 7 18:27:30 2010
$ kill -9 8224
[1]+ Killed python signal1.py 12
Ввод и вывод здесь несколько перемешаны, потому что процесс осуществляет вывод на тот же экран, в котором вводятся новые команды оболочки. Послать программе сигнал можно с помощью команды оболочки kill, которая принимает номер сигнала и ID процесса (8224). Всякий раз, когда очередная команда kill посылает сигнал, процесс отвечает сообщением, сгенерированным функцией обработчика сигнала. Сигнал с номером 9 всегда завершает процесс.
Модуль signal экспортирует также функцию signal.alarm, с помощью которой определяется интервал времени в секундах, по истечении которого должен быть отправлен сигнал SIGALRM. Чтобы определить максимальное время ожидания и обработать ситуацию его превышения, достаточно установить обработчик сигнала SIGALRM и вызвать функцию signal.alarm, как показано в примере 5.28.
Пример 5.28. PP4E\System\Processes\signal2.py
установка сигналов по истечении времени ожидания и их обработка на языке Python; функция time.sleep некорректно ведет себя при появлении сигнала SIGALARM (как и любого другого сигнала на моем компьютере, работающем под управлением Linux), поэтому здесь вызывается функция signal.pause, которая приостанавливает выполнение сценария до получения сигнала;
import sys, signal, time
def now(): return time.asctime()
def onSignal(signum, stackframe): # обработчик сигнала
print(‘Got alarm’, signum, ‘at’, now()) # большинство обработчиков
# остаются действующими while True:
print(‘Setting at’, now())
signal.signal(signal.SIGALRM, onSignal) # установить обработчик сигнала signal.alarm(5) # послать сигнал через 5 секунд
signal.pause() # ждать сигнала
После запуска этого сценария под управлением Cygwin в Windows функция обработчика onSignal будет вызываться каждые пять секунд:
[C:\…\PP4E\System\Processes]$ python signal2.py
Setting at Sun Mar 7 18:37:10 2010
Got alarm 14 at Sun Mar 7 18:37:15 2010
Setting at Sun Mar 7 18:37:15 2010
Got alarm 14 at Sun Mar 7 18:37:20 2010
Setting at Sun Mar 7 18:37:20 2010
Got alarm 14 at Sun Mar 7 18:37:25 2010
Setting at Sun Mar 7 18:37:25 2010
Got alarm 14 at Sun Mar 7 18:37:30 2010
Setting at Sun Mar 7 18:37:30 2010
…Ctrl-C для выхода…
Вообще говоря, сигналы следует использовать осторожно, что не явствует из приведенных примеров. В частности, некоторые системные вызовы плохо реагируют на прерывание сигналами, а в многопоточной программе только главный поток может устанавливать обработчики сигналов и реагировать на них.
Однако при правильном использовании сигналы предоставляют механизм связи, основанный на событиях. Он не такой мощный, как потоки ввода-вывода данных типа каналов, но в некоторых ситуациях его достаточно, например, когда нужно только сообщить программе, что произошло нечто важное, не передавая подробностей о самом событии. Иногда сигналы используют в комбинации с другими инструментами IPC. Например, начальный сигнал может сообщить программе, что клиент хочет установить связь через именованный канал, — примерно как если похлопать кого-то по плечу, чтобы привлечь его внимание, прежде чем начать говорить. На большинстве платформ резервируется один или несколько номеров сигналов SIGUSR для определяемых пользователем событий такого рода. Такой способ интеграции иногда может служить альтернативой обращения к блокируемой функции ввода в дочернем потоке выполнения.
Обратите также внимание на функцию os.kill(pid, sig), которая передает сигналы известным процессам из сценариев на языке Python в Unix-подобных системах, — она очень похожа на команду kill оболочки, использованную нами выше. Необходимый идентификатор ID можно получить из значения, возвращаемого функцией os.fork, порождающей дочерний процесс, или с помощью других функций. Как и os.fork, эта функция доступна в версии Cygwin Python, но она отсутствует в стандартной версии Python для Windows. Кроме того, смотрите обсуждение приема использования сигналов для удаления процессов «зомби» в главе 12.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011