Автор оригинала: Doug Hellmann.
Цель:
Асинхронные системные события
Сигналы – это функция операционной системы, которая обеспечивает средство уведомления программы о событии и ее асинхронной обработки. Они могут быть созданы самой системой или отправлены из одного процесса в другой. Поскольку сигналы прерывают регулярный поток программы, возможно, что некоторые операции (особенно ввод-вывод) могут вызвать ошибки, если сигнал будет получен в середине.
Сигналы идентифицируются целыми числами и определяются в заголовках операционной системы C. Python представляет сигналы, соответствующие платформе, в виде символов в модуле signal
. В примерах в этом разделе используются SIGINT
и SIGUSR1
. Оба обычно определены для всех Unix и Unix-подобных систем.
Примечание
Программирование с помощью обработчиков сигналов Unix – нетривиальное занятие. Это введение, и оно не включает все детали, необходимые для успешного использования сигналов на каждой платформе. Существует некоторая степень стандартизации версий Unix, но есть и некоторые вариации, поэтому обратитесь к документации по операционной системе, если у вас возникнут проблемы.
Получение сигналов
Как и в других формах программирования, основанного на событиях, сигналы принимаются путем создания функции обратного вызова, называемой обработчиком сигнала , которая вызывается при возникновении сигнала. Аргументами обработчика сигнала являются номер сигнала и кадр стека из той точки программы, которая была прервана сигналом.
signal_signal.py
import signal import os import time def receive_signal(signum, stack): print('Received:', signum) # Register signal handlers signal.signal(signal.SIGUSR1, receive_signal) signal.signal(signal.SIGUSR2, receive_signal) # Print the process ID so it can be used with 'kill' # to send this program signals. print('My PID is:', os.getpid()) while True: print('Waiting...') time.sleep(3)
Этот пример скрипта повторяется бесконечно, каждый раз делая паузу на несколько секунд. Когда приходит сигнал, вызов sleep ()
прерывается, и обработчик сигнала receive_signal
печатает номер сигнала. После возврата обработчика сигнала цикл продолжается.
Отправляйте сигналы работающей программе с помощью os.kill ()
или программы командной строки Unix kill
.
$ python3 signal_signal.py My PID is: 71387 Waiting... Waiting... Waiting... Received: 30 Waiting... Waiting... Received: 31 Waiting... Waiting... Traceback (most recent call last): File "signal_signal.py", line 28, intime.sleep(3) KeyboardInterrupt
Предыдущий вывод был получен путем запуска signal_signal.py
в одном окне, а затем в другом окне:
$ kill -USR1 $pid $ kill -USR2 $pid $ kill -INT $pid
Получение зарегистрированных обработчиков
Чтобы узнать, какие обработчики сигналов зарегистрированы для сигнала, используйте getsignal ()
. Передайте номер сигнала в качестве аргумента. Возвращаемое значение – зарегистрированный обработчик или одно из специальных значений SIG_IGN
(если сигнал игнорируется), SIG_DFL
(если используется поведение по умолчанию) или Нет
(если существующий обработчик сигналов был зарегистрирован на C, а не на Python).
signal_getsignal.py
import signal def alarm_received(n, stack): return signal.signal(signal.SIGALRM, alarm_received) signals_to_names { getattr(signal, n): n for n in dir(signal) if n.startswith('SIG') and '_' not in n } for s, name in sorted(signals_to_names.items()): handler signal.getsignal(s) if handler is signal.SIG_DFL: handler 'SIG_DFL' elif handler is signal.SIG_IGN: handler 'SIG_IGN' print('{:<10} ({:2d}):'.format(name, s), handler)
Опять же, поскольку для каждой ОС могут быть определены разные сигналы, вывод в других системах может отличаться. Это из OS X:
$ python3 signal_getsignal.py SIGHUP ( 1): SIG_DFL SIGINT ( 2):SIGQUIT ( 3): SIG_DFL SIGILL ( 4): SIG_DFL SIGTRAP ( 5): SIG_DFL SIGIOT ( 6): SIG_DFL SIGEMT ( 7): SIG_DFL SIGFPE ( 8): SIG_DFL SIGKILL ( 9): None SIGBUS (10): SIG_DFL SIGSEGV (11): SIG_DFL SIGSYS (12): SIG_DFL SIGPIPE (13): SIG_IGN SIGALRM (14): SIGTERM (15): SIG_DFL SIGURG (16): SIG_DFL SIGSTOP (17): None SIGTSTP (18): SIG_DFL SIGCONT (19): SIG_DFL SIGCHLD (20): SIG_DFL SIGTTIN (21): SIG_DFL SIGTTOU (22): SIG_DFL SIGIO (23): SIG_DFL SIGXCPU (24): SIG_DFL SIGXFSZ (25): SIG_IGN SIGVTALRM (26): SIG_DFL SIGPROF (27): SIG_DFL SIGWINCH (28): SIG_DFL SIGINFO (29): SIG_DFL SIGUSR1 (30): SIG_DFL SIGUSR2 (31): SIG_DFL
Отправка сигналов
Функция отправки сигналов из Python – это os.kill ()
. Его использование описано в разделе, посвященном модулю os, Создание процессов с помощью os.fork ().
Будильники
Тревоги – это особый вид сигнала, когда программа просит ОС уведомить ее по истечении некоторого периода времени. Как указано в стандартной документации модуля для os, это полезно для избежания блокировки на неопределенное время при операции ввода-вывода или другом системном вызове.
signal_alarm.py
import signal import time def receive_alarm(signum, stack): print('Alarm :', time.ctime()) # Call receive_alarm in 2 seconds signal.signal(signal.SIGALRM, receive_alarm) signal.alarm(2) print('Before:', time.ctime()) time.sleep(4) print('After :', time.ctime())
В этом примере вызов sleep ()
прерывается, но затем продолжается после обработки сигнала, поэтому сообщение, напечатанное после возврата sleep ()
, показывает, что программа была приостановлена. по крайней мере, пока продолжительность сна.
$ python3 signal_alarm.py Before: Sat Apr 22 14:48:57 2017 Alarm : Sat Apr 22 14:48:59 2017 After : Sat Apr 22 14:49:01 2017
Игнорирование сигналов
Чтобы игнорировать сигнал, зарегистрируйте SIG_IGN
в качестве обработчика. Этот сценарий заменяет обработчик по умолчанию для SIGINT
на SIG_IGN
и регистрирует обработчик для SIGUSR1
. Затем он использует signal.pause ()
для ожидания получения сигнала.
signal_ignore.py
import signal import os import time def do_exit(sig, stack): raise SystemExit('Exiting') signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGUSR1, do_exit) print('My PID:', os.getpid()) signal.pause()
Обычно SIGINT
(сигнал, отправляемый оболочкой программе, когда пользователь нажимает Ctrl-C
) вызывает KeyboardInterrupt
. Этот пример игнорирует SIGINT
и вызывает SystemExit
, когда видит SIGUSR1
. Каждый ^ C
в выходных данных представляет собой попытку использовать Ctrl-C
для уничтожения сценария с терминала. Использование kill -USR1 72598
из другого терминала в конечном итоге приводит к завершению сценария.
$ python3 signal_ignore.py My PID: 72598 ^C^C^C^CExiting
Сигналы и потоки
Сигналы и потоки обычно плохо сочетаются, потому что только основной поток процесса будет получать сигналы. В следующем примере настраивается обработчик сигнала, он ожидает сигнала в одном потоке и отправляет сигнал из другого.
signal_threads.py
import signal import threading import os import time def signal_handler(num, stack): print('Received signal {} in {}'.format( num, threading.currentThread().name)) signal.signal(signal.SIGUSR1, signal_handler) def wait_for_signal(): print('Waiting for signal in', threading.currentThread().name) signal.pause() print('Done waiting') # Start a thread that will not receive the signal receiver threading.Thread( targetwait_for_signal, name'receiver', ) receiver.start() time.sleep(0.1) def send_signal(): print('Sending signal in', threading.currentThread().name) os.kill(os.getpid(), signal.SIGUSR1) sender threading.Thread(targetsend_signal, name'sender') sender.start() sender.join() # Wait for the thread to see the signal (not going to happen!) print('Waiting for', receiver.name) signal.alarm(2) receiver.join()
Все обработчики сигналов были зарегистрированы в основном потоке, потому что это требование реализации модуля signal
для Python, независимо от поддержки базовой платформой для смешивания потоков и сигналов. Хотя поток-получатель вызывает signal.pause ()
, он не принимает сигнал. Вызов signal.alarm (2)
в конце примера предотвращает бесконечный блок, поскольку поток-получатель никогда не завершится.
$ python3 signal_threads.py Waiting for signal in receiver Sending signal in sender Received signal 30 in MainThread Waiting for receiver Alarm clock
Хотя аварийные сигналы могут быть установлены в любом потоке, они всегда принимаются основным потоком.
signal_threads_alarm.py
import signal import time import threading def signal_handler(num, stack): print(time.ctime(), 'Alarm in', threading.currentThread().name) signal.signal(signal.SIGALRM, signal_handler) def use_alarm(): t_name threading.currentThread().name print(time.ctime(), 'Setting alarm in', t_name) signal.alarm(1) print(time.ctime(), 'Sleeping in', t_name) time.sleep(3) print(time.ctime(), 'Done with sleep in', t_name) # Start a thread that will not receive the signal alarm_thread threading.Thread( targetuse_alarm, name'alarm_thread', ) alarm_thread.start() time.sleep(0.1) # Wait for the thread to see the signal (not going to happen!) print(time.ctime(), 'Waiting for', alarm_thread.name) alarm_thread.join() print(time.ctime(), 'Exiting normally')
Сигнал тревоги не прерывает вызов sleep ()
в use_alarm ()
.
$ python3 signal_threads_alarm.py Sat Apr 22 14:49:01 2017 Setting alarm in alarm_thread Sat Apr 22 14:49:01 2017 Sleeping in alarm_thread Sat Apr 22 14:49:01 2017 Waiting for alarm_thread Sat Apr 22 14:49:02 2017 Alarm in MainThread Sat Apr 22 14:49:04 2017 Done with sleep in alarm_thread Sat Apr 22 14:49:04 2017 Exiting normally
Смотрите также
- стандартная библиотека документации для сигнала
- PEP 475 – повторить системные вызовы с ошибкой EINTR
- subprocess – Еще примеры отправки сигналов процессам.
- Создание процессов с помощью os.fork () – функцию
kill ()
можно использовать для отправки сигналов между процессами.