signal - Eventi di Sistema Asincroni
Scopo: Eventi di Sistema Asincroni
I segnali sono una caratteristica di un sistema operativo che fornisce mezzi per notificare un programma di un evento, e se è stato gestito in modo asincrono. Possono essere generati dal sistema stesso, oppure inviati da un processo a un altro. Visto che i segnali interrompono il flusso regolare di un programma, è possibile che alcune operazioni (in particolare di Input/Output) possano produrre errori se un segnale viene ricevuto durante il loro svolgimento.
I segnali sono identificati da interi e sono definiti nelle intestazioni C del sistema operativo. Python espone i segnali appropriati alla piattaforma come simboli nel modulo signal. Gli esempi in questa sezione usano SIGINT
e SIGUSR1
. Entrambi sono tipicamente definiti per tutti i sistemi Unix e similari.
Ricevere Segnali
Così come altre forme di programmazione basate sugli eventi, i segnali sono ricevuti impostando una funzione di callback, chiamata gestore di segnale, che viene invocata quando il segnale si manifesta. Gli argomenti per il gestore di segnale sono il numero di segnale e lo stack frame a partire dal punto nel programma che è stato interrotto dal segnale.
# signal_signal.py
import signal
import os
import time
def receive_signal(signum, stack):
print('Ricevuto:', signum)
# Registra i gestori di segnale
signal.signal(signal.SIGUSR1, receive_signal)
signal.signal(signal.SIGUSR2, receive_signal)
# Stampa l'ID del processo in modo che possa essere usato con 'kill'
# per inviare segnali a questo programma
print('Il mio PID è:', os.getpid())
while True:
print('In attesa...')
time.sleep(3)
Questo script di esempio esegue un ciclo infinito, mettendosi in pausa per pochi secondi ogni volta. Quando entra un segnale, la chiamata a sleep()
viene interrotta e il gestore di segnale receive_signal
stampa il numero del segnale. Dopo che il gestore di segnale ritorna, il ciclo continua.
Si mandino segnali al programma in esecuzione tramite os.kill()
oppure con il programma da riga di comando Unix kill
.
$ python3 signal_signal.py Il mio PID è: 7388 In attesa... In attesa... In attesa... Ricevuto: 10 In attesa... In attesa... Ricevuto: 12 In attesa... In attesa... In attesa... Traceback (most recent call last): File "signal_signal.py", line 21, in <module> time.sleep(3) KeyboardInterrupt
L'output precedente è stato ottenuto eseguendo signal_signal.py
in una finestra, mentre in un altra si eseguiva:
$ kill -USR1 <numero_pid> $ kill -USR2 <numero_pid> $ kill -INT <numero_pid>
Recuperare Gestori Registrati
Per vedere quali gestori di segnale sono registrati per un segnale si usa getsignal()
. Si passa il numero di segnale come argomento. Il valore ritornato è il gestore registrato, oppure uno dei valori speciali SIG_IGN
(se il segnale è stato ignorato), SIG_DFL
(se viene utilizzato il comportamento predefinito), oppure None
(se il gestore di segnale esistente era stato registrato da C, invece che da 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)
Ancora una volta, visto che ogni sistema operativo potrebbe avere definiti segnali diversi, l'output su altri sistemi potrebbe variare. Questo è da Unix:
$ 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 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 0x7f08d2387048> 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
Inviare Segnali
La funzione per inviare segnali da Python è os.kill()
. Il suo uso è trattato nella sezione del modulo os, Creare Processi con os.fork().
Allarmi
Gli allarmi sono un tipo speciale di segnale, dove il programma chiede al sistema operativo di notificarlo dopo che è trascorso un certo periodo di tempo. Come evidenziato dalla documentazione standard del modulo os, la cosa è utile per evitare di bloccare all'infinito una operazione di Input/Output o altra chiamata di sistema.
# signal_alarm.py
import signal
import time
def receive_alarm(signum, stack):
print('Allarme :', time.ctime())
# Chiam receive_alarm in 2 secondi
signal.signal(signal.SIGALRM, receive_alarm)
signal.alarm(2)
print('Prima:', time.ctime())
time.sleep(4)
print('Dopo :', time.ctime())
In questo esempio, la chiamata a sleep()
viene interrotta, poi riprende dopo che il segnale è elaborato, quindi il messaggio stampato dopo il ritorno di sleep()
mostra che il programma è stato messo in pausa per almeno tanto quanto la durata della pausa stessa.
$ python3 signal_alarm.py Prima: Tue Jan 31 21:24:15 2017 Allarme : Tue Jan 31 21:24:17 2017 Dopo : Tue Jan 31 21:24:19 2017
Ignorare i Segnali
Per ignorare un segnale, si registra SIG_IGN
come gestore. Questo script sostituisce il gestore predefinito per SIGINT
con SIG_IGN
e registra un gestore per SIGUSR1
. Poi usa signal.pause()
per attendere che il segnale sia ricevuto.
# signal_ignore.py
import signal
import os
import time
def do_exit(sig, stack):
raise SystemExit('In uscita')
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGUSR1, do_exit)
print('Il mio PID:', os.getpid())
signal.pause()
Normalmente SIGINT
(il segnale inviato dalla shell a un programma quando l'utente preme Ctrl-C
) solleva una eccezione KeyboardInterrupt
. Questo esempio ignora SIGINT
e solleva SystemExit
quando vede SIGUSR1
. Ogni ^C
nell'output rappresenta il tentativo di usare Ctrl-C
per terminare lo script dal terminale. Usando kill -USR1 26880
da un altro terminale si causerà l'uscita dello script.
$ python3 signal_ignore.py Il mio PID: 26880 ^C^C^CIn uscita
Segnali e Thread
Segnali e thread in genere non si combinano bene in quanto solo il thread principale riceverà segnali. L'esempio di seguito imposta un gestore di segnale, attende il segnale in un thread, e invia il segnale da un altro.
# signal_threads.py
import signal
import threading
import os
import time
def signal_handler(num, stack):
print('Segnale {} ricevuto in {}'.format(
num, threading.currentThread().name))
signal.signal(signal.SIGUSR1, signal_handler)
def wait_for_signal():
print('In attesa dell\'entrata del segnale',
threading.currentThread().name)
signal.pause()
print('Attesa terminata')
# Inizia un thread che non riceverà il segnale
receiver = threading.Thread(
target=wait_for_signal,
name='ricevente',
)
receiver.start()
time.sleep(0.1)
def send_signal():
print('Invio del segnale', threading.currentThread().name)
os.kill(os.getpid(), signal.SIGUSR1)
sender = threading.Thread(target=send_signal, name='mittente')
sender.start()
sender.join()
# Attende il thread per vedere il segnale (non succederà!)
print('In attesa di', receiver.name)
signal.alarm(2)
receiver.join()
I gestori di segnale sono tutti registrati nel thread principale in quanto questo è un requisito dell'implementazione per Python del modulo signal, a prescindere dal supporto per l'integrazione di thread e segnali della piattaforma sottostante. Anche se il thread ricevente chiama signal.pause()
non riceve il segnale. La chiamata di signal.alarm(2)
verso la fine dell'esempio previene un ciclo infinito, visto che il thread ricevente non uscirà mai.
$ python3 signal_threads.py In attesa dell'entrata del segnale ricevente Invio del segnale mittente Segnale 10 ricevuto in MainThread In attesa di ricevente Alarm clock
Sebbene si possano impostare allarmi in qualunque thread, essi saranno sempre ricevuti dal thread principale.
# signal_threads_alarm.py
import signal
import threading
import time
def signal_handler(num, stack):
print(time.ctime(), 'Allarme in',
threading.currentThread().name)
signal.signal(signal.SIGALRM, signal_handler)
def use_alarm():
t_name = threading.currentThread().name
print(time.ctime(), 'Impostazione allarme in', t_name)
signal.alarm(1)
print(time.ctime(), 'In pausa per', t_name)
time.sleep(3)
print(time.ctime(), 'Pausa terminata in', t_name)
# Inizia un thread che non riceverà il segnale
alarm_thread = threading.Thread(
target=use_alarm,
name='alarm_thread',
)
alarm_thread.start()
time.sleep(0.1)
# Attende il thread per vedere il signale (non succederà!)
print(time.ctime(), 'In attesa di', alarm_thread.name)
alarm_thread.join()
print(time.ctime(), 'Uscita normale')
L'allarme non interrompe la chiamata di sleep()
in use_alarm
.
$ python3 signal_threads_alarm.py Fri Feb 3 20:39:25 2017 Impostazione allarme in alarm_thread Fri Feb 3 20:39:25 2017 In pausa per alarm_thread Fri Feb 3 20:39:26 2017 In attesa di alarm_thread Fri Feb 3 20:39:26 2017 Allarme in MainThread Fri Feb 3 20:39:28 2017 Pausa terminata in alarm_thread Fri Feb 3 20:39:28 2017 Uscita normale
Vedere anche:
- signal
- La documentazione della libreria standard per questo modulo.
- PEP 475
- Falliti tentativi di ripetere chiamate di sistema con EINTR
- subprocess
- Genera processi addizionali
- Creare Processi con os.fork()
- La funzione
kill()
può essere usata per inviare segnali tra processi