signal — Eventos asíncronos del sistema¶
Propósito: | Eventos asíncronos del sistema |
---|
Las señales son una característica del sistema operativo que proporciona un medio de notificar a un programa de un evento, y manejarlo de forma asíncrona. Pueden ser generados por el propio sistema o enviados desde un proceso a otro. Dado que las señales interrumpen el flujo regular del programa, es posible que algunas operaciones (especialmente I/O) produzcan errores si se recibe una señal en el medio.
Las señales se identifican mediante números enteros y se definen en las
cabeceras C del sistema operativo. Python expone las señales apropiadas para
el plataforma como símbolos en el módulo signal
. Los ejemplos en este
sección utilizan SIGINT
y SIGUSR1
. Ambas son típicamente definidas para
todos los sistemas Unix y Unix.
Nota
La programación con manejadores de señales Unix no es un esfuerzo trivial. Esta es una introducción, y no incluye todas los detalles necesarios para utilizar señales con éxito en cada plataforma. Hay cierto grado de estandarización en las versiones de Unix, pero también hay algunas variaciones, así que consulta el documentación del sistema operativo si tienes problemas.
Recibir señales¶
Al igual que con otras formas de programación basada en eventos, se reciben señales estableciendo una función de devolución de llamada, llamada un manejador de señal, que se invoca cuando se produce la señal. Los argumentos al manejador de señal son el número de señal y la pila desde el punto en el programa que fue interrumpido por la señal.
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)
Esta secuencia de comandos de ejemplo se repite indefinidamente, deteniéndose
unos segundos cada vez. Cuando llega una señal, la llamada sleep()
se
interrumpe y el manejador de señal receive_signal
imprime el número de la
señal. Una vez que el manejador de señal regresa, el bucle continúa.
Envía señales al programa en ejecución usando os.kill()
o el programa de
linea de comando 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 <module>
time.sleep(3)
KeyboardInterrupt
La salida anterior se produjo ejecutando signal_signal.py
en una ventana,
luego corriendo en otra ventana:
$ kill -USR1 $pid
$ kill -USR2 $pid
$ kill -INT $pid
Recuperar manejadores registrados¶
Para ver qué manejadores de señales están registrados para una señal, usa
getsignal()
. Pasa el número de señal como argumento. El valor de regreso
es el controlador registrado, o uno de los valores especiales SIG_IGN
(si
la señal está siendo ignorada), SIG_DFL
(si se está utilizando el
comportamiento predeterminado), o None
(si el controlado de señales el
controlador de señales existente se registró desde C, en lugar de Python).
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)
Nuevamente, dado que cada sistema operativo puede tener diferentes señales definidas, la salida en otros sistemas pueden variar. Esto es de macOS X:
$ python3 signal_getsignal.py
SIGHUP ( 1): SIG_DFL
SIGINT ( 2): <built-in function default_int_handler>
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): <function alarm_received at 0x1019a6a60>
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
Enviar señales¶
La función para enviar señales desde Python es os.kill()
. Su uso está
cubierto en la sección sobre el módulo os
Crear procesos con os.fork().
Alarmas¶
Las alarmas son un tipo especial de señal, donde el programa le pide al sistema
operativo que lo notifique después de que haya transcurrido algún tiempo. Como
la documentación estándar del módulo os
señala, esto es útil para evitar
el bloqueo indefinido en una operación de E/S u otra llamada del sistema.
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())
En este ejemplo, la llamada a sleep()
se interrumpe, pero continúa después
de que la señal se procesa, por lo que el mensaje se imprime después que
sleep()
muestra que el programa se pausó al menos durante la duración de
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
Ignorar Señales¶
Para ignorar una señal, registra SIG_IGN
como el controlador. Esta
secuencia de comandos reemplaza el controlador predeterminado para SIGINT
con SIG_IGN
, y registra un manejador para SIGUSR1
. Entonces utiliza
signal.pause()
para esperar a que se reciba una señal.
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()
Normalmente SIGINT
(la señal enviada por el shell a un programa cuando el
usuario presiona Ctrl-C
) levanta un KeyboardInterrupt
. Este ejemplo
ignora SIGINT
y genera SystemExit
cuando ve SIGUSR1
. Cada ^C
en la salida representa un intento de usar Ctrl-C
para matar la secuencia
de comandos desde la terminal. El uso de kill -USR1 72598
desde otro
terminal eventualmente causa que la secuencia de comandos termine.
$ python3 signal_ignore.py
My PID: 72598
^C^C^C^CExiting
Señales e hilos¶
Las señales y los hilos generalmente no se mezclan bien porque solo el hilo principal de un proceso recibirá señales. El siguiente ejemplo establece un manejador de señal, espera la señal en un hilo y envía la señal de otro.
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(
target=wait_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(target=send_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()
Los manejadores de señales fueron todos registrados en el hilo principal porque
este es un requisito de la implementación del módulo signal
para Python,
independientemente del soporte de la plataforma subyacente para mezclar hilos y
señales. Aunque el subproceso del receptor llama a signal.pause()
, no
recibe la señal. La llamada signal.alarm(2)
cerca de el final del ejemplo
evita un bloque infinito, ya que el receptor el hilo nunca saldrá.
$ python3 signal_threads.py
Waiting for signal in receiver
Sending signal in sender
Received signal 30 in MainThread
Waiting for receiver
Alarm clock
Aunque las alarmas se pueden configurar en cualquier hilo, siempre son recibidas por el hilo principal.
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(
target=use_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')
La alarma no cancela la llamada sleep()
en 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
Ver también
- Documentación de la biblioteca estándar para signal
- PEP 475 – Vuelva a intentar las llamadas del sistema que fallan con EINTR
subprocess
– Más ejemplos de envío de señales a procesos.- Crear procesos con os.fork() – La función
kill()
se puede utilizar para enviar señales entre procesos.