Рубрики
Без рубрики

signal – Асинхронные системные события

Автор оригинала: 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, in 
    time.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

Смотрите также