PyMOTW: signal
19 Agosto 2008
Traducción de PyMOTW: signal el módulo
signal de la columna semanal de Doug Hellmann.
Recibe notificaciones de eventos asíncronos del sistema con el módulo signal.
| Módulo: | signal |
|---|---|
| Propósito: | Manejar eventos asíncronos. |
| Versión de Python: | 1.4 y posterior |
Descripción:
Programar con manipuladores de señales Unix es un esfuerzo nada trivial. Ésta es una introducción y no incluye todos los detalles que puedes necesitar para usar exitosamente señales en cualquier plataforma. Hay una cierta estandarización en las versiones de Unix, pero también hay también algunas diferencias, así que consulta la documentación de tu sistema operativo si tienes problemas.
Señales son medios para notificar a tu programa de un evento, y manipularlo asíncronamente. Pueden ser generadas por el mismo sistema, o enviadas desde un proceso a otro, es posible que algunas operaciones (especialmente de entrada/salida, I/O) puedan producir errores si reciben una señal en el medio (de su ejecución).
Las señales están identificadas por número enteros y son definidas en los
encabezados C del sistema operativo. Python define las señales apropiadas para
la plataforma como símbolos en el módulo signal. En los ejemplos a
continuación, voy a usar SIGINT y SIGUSR1. Ambas están normalmente
definidas para todos lo sistemas Unix y similares a Unix.
Recibiendo señales:
Como con otras formas de programación basada en eventos, las señales se reciben estableciendo una función de retorno (callback), invocada por el manipulador de la señal, que es invocada cuando la señal ocurre. Los argumentos para tu manipulador de señal son el número de la señal y el marco de la pila (stack frame) del punto en tu programa que fue interrumpido por la señal.
import signal
import os
import time
def receive_signal(signum, stack):
print 'Recibida', signum
signal.signal(signal.SIGUSR1, receive_signal)
signal.signal(signal.SIGUSR2, receive_signal)
print 'Mi PID es:', os.getpid()
while True:
print 'Esperando...'
time.sleep(3)
Este ejemplo relativamente simple repite el bucle indefinidamente, pausando por
unos cuantos segundos cada vez. Cuando una señal llega, la invocación de
sleep es interrumpida y el manipulador de la señal receive_signal() muestra
el número de la señal. Cuando el manipulador de la señal retorna, el bucle
continua.
Para enviar señales a programas en ejecución, yo uso el programa de línea de
comandos kill. Para producir el resultado a continuación, ejecuto
signal_signal.py en una ventana, luego kill -USR1 7810, kill -USR2 7810 y
kill -INT 7810 en otra.
$ python signal_signal.py
Mi PID es: 7810
Esperando...
Esperando...
Recibida 10
Esperando...
Esperando...
Recibida 12
Esperando...
Esperando...
Traceback (most recent call last):
File "signal_signal.py", line 25, in <module>
time.sleep(3)
KeyboardInterrupt
getsignal():
Para ver qué manipulador están registrados para una señal, usa getsignal().
Pasa el número de la señal como argumento. El valor de retorno es el
manipulador registrado, o uno de los valores especiales signa.SIG_IGN (si la
señal está siendo ignorada), signal.SIG_DFL (si el comportamiento por defecto
es usado), o None (si el manipulador existente de la señal fue registrado
desde C, en lugar de Python).
import signal
def alarm_received(n, stack):
return
signal.signal(signal.SIGALRM, alarm_received)
signals_to_names = {}
for n in dir(signal):
if n.startswith('SIG') and not n.startswith('SIG_'):
signals_to_names[getattr(signal, n)] = 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 '%-10s (%2d):' % (name, s), handler
Una vez más, ya que cada sistema operativo define distintas señales, el resultado que ves ejecutando ésto en otros sistemas puede variar. Ésto es Linux:
$ python 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
SIGBUS ( 7): SIG_DFL
SIGFPE ( 8): SIG_DFL
SIGKILL ( 9): SIG_DFL
SIGUSR1 (10): SIG_DFL
SIGSEGV (11): SIG_DFL
SIGUSR2 (12): SIG_DFL
SIGPIPE (13): SIG_IGN
SIGALRM (14): <function alarm_received at 0xb7dd2fb4>
SIGTERM (15): SIG_DFL
SIGCLD (17): SIG_DFL
SIGCONT (18): SIG_DFL
SIGSTOP (19): SIG_DFL
SIGTSTP (20): SIG_DFL
SIGTTIN (21): SIG_DFL
SIGTTOU (22): SIG_DFL
SIGURG (23): SIG_DFL
SIGXCPU (24): SIG_DFL
SIGXFSZ (25): SIG_IGN
SIGVTALRM (26): SIG_DFL
SIGPROF (27): SIG_DFL
SIGWINCH (28): SIG_DFL
SIGPOLL (29): SIG_DFL
SIGPWR (30): SIG_DFL
SIGSYS (31): SIG_DFL
SIGRTMIN (34): SIG_DFL
SIGRTMAX (64): SIG_DFL
Enviando señales:
La función para enviar señales es os.kill(). Su uso está descrito en el
artículo PyMOTW que describe el módulo
os
(La traducción del artículo sobre el módulo os estará disponible pronto.)
Alarmas:
Alarmas son una clase algo especial de señales, donde tu programa le pide al sistema operativo que lo notifique después de que un período de tiempo ha transcurrido. Como la documentación estándar del módulo señala, ésto es útil para evitar bloquearse indefinidamente en una operación de entrada/salida (I/O) u otra operación del sistema operativo.
import signal
import time
def receive_alarm(signum, stack):
print 'Alarma :', time.ctime()
# Invoca receive_alarm luego de 2 segundos
signal.signal(signal.SIGALRM, receive_alarm)
signal.alarm(2)
print 'Antes :', time.ctime()
time.sleep(4)
print 'Después :', time.ctime()
En este ejemplo, la invocación de sleep() no dura los 4 segundos completos.
$ python signal_alarm.py
Antes : Tue Aug 19 10:53:19 2008
Alarma : Tue Aug 19 10:53:21 2008
Después : Tue Aug 19 10:53:21 2008
Ignorando Señales:
Para ignorar una señal, registra SIG_IGN como el manipulador. Este script
remplaza el manipulador por defecto para SIGINT con SIG_IGN, y registra un
manipulador para SIGUSR1. Luego usa signal.pause() para esperar una señal.
import signal
import os
import time
def do_exit(sig, stack):
raise SystemExit('Saliendo')
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGUSR1, do_exit)
print 'Mi PID:', os.getpid()
signal.pause()
Normalmente SIGINT (la señal enviada por la línea de comandos a tu programa
cuando presionas Control-C) eleva KeyboardInterrupt. En este caso, ignoramos
SIGINT y elevamos SystemExit cuando vemos SIGUSR1. Cada ^C representa un
intento de usar Control-C para terminar el script desde la terminal. Usando
kill -USR1 7820 desde otra terminal ocasiona que el script finalice.
$ python signal_ignore.py
My PID: 7820
^C^C^CSaliendo
Señales e hilos de ejecución (threads):
Generalmente señales e hilos de ejecución no combinan bien. Sólo el hilo principal de ejecución de un proceso recibe señales, entonces generalmente no es útil usarlas en hilos de ejecución. El siguiente ejemplo de un manipulador de señales espera por una señal en un hilo de ejecución, y envía la señal desde otro.
import signal
import threading
import os
import time
def signal_handler(num, stack):
print 'Recibe señal %d en %s' % (num, threading.currentThread())
signal.signal(signal.SIGUSR1, signal_handler)
def wait_for_signal():
print 'Esperando por señal en', threading.currentThread()
signal.pause()
print 'Dejando de esperar'
# Inicia un hilo que no recibirá la señal
receiver = threading.Thread(target=wait_for_signal, name='receiver')
receiver.start()
time.sleep(0.1)
def send_signal():
print 'Enviando señal en', threading.currentThread()
os.kill(os.getpid(), signal.SIGUSR1)
sender = threading.Thread(target=send_signal, name='sender')
sender.start()
sender.join()
# Espera que el hilo vea la señal (no va a suceder!)
print 'Esperando por', receiver
signal.alarm(2)
receiver.join()
Nota que todos los manipuladores de señal fueron registrados en el hilo
principal. Este es un requerimiento de la implementación del módulo signal
para Python, sin importar el soporte subyacente de plataforma mezclar hilos de
ejecución y señales. Aunque el hilo receptor invoca signal.pause(), no
recibe la señal. La invocación signal.alarm(2) cerca al final del ejemplo
evita un bloqueo sin fin, ya que el hilo receptor nunca terminará.
$ python signal_threads.py
Esperando por señal en <Thread(receiver, started)>
Enviando señal en <Thread(sender, started)>
Recibe señal 10 en <_MainThread(MainThread, started)>
Esperando por <Thread(receiver, started)>
Alarm clock
Si bien se puede definir alarmas en hilos de ejecución, también son recibidas por el hilo principal.
import signal
import time
import threading
def signal_handler(num, stack):
print time.ctime(), 'Alarma en', threading.currentThread()
signal.signal(signal.SIGALRM, signal_handler)
def use_alarm():
print time.ctime(), 'Ajustando alarma en', threading.currentThread()
signal.alarm(1)
print time.ctime(), 'Durmiendo en', threading.currentThread()
time.sleep(3)
print time.ctime(), 'Dejando de dormir'
# Inicia un hilo que no recibirá la señal
alarm_thread = threading.Thread(target=use_alarm, name='alarm_thread')
alarm_thread.start()
time.sleep(0.1)
# Espera que el hilo vea la señal (no va a suceder!)
print time.ctime(), 'Esperando por', alarm_thread
alarm_thread.join()
print time.ctime(), 'Saliendo normalmente'
Nota que la alarma no aborta la invocación de sleep() en use_alarm().
$ python signal_threads_alarm.py
Tue Aug 19 11:49:47 2008 Ajustando alarma en <Thread(alarm_thread, started)>
Tue Aug 19 11:49:47 2008 Durmiendo en <Thread(alarm_thread, started)>
Tue Aug 19 11:49:47 2008 Esperando por <Thread(alarm_thread, started)>
Tue Aug 19 11:49:50 2008 Dejando de dormir
Tue Aug 19 11:49:50 2008 Alarma en <_MainThread(MainThread, started)>
Tue Aug 19 11:49:50 2008 Saliendo normalmente
Referencias:
Python Module of the Week Home
Descarga el código
Copyright 2008 Doug Hellmann.
blog comments powered by Disqus
