Interrupts
Interrupts werden üblicherweise in Computersystemen benutzt, um den üblichen Ablauf zu unterbrechen und einen besonderen Zustand zu bearbeiten. So erzeugt jeder Tastendruck auf der Tastatur einen Interrupt, der dem Betriebssystem signalisiert, dass Daten vorliegen, die verarbeitet werden müssen. Gleiches gilt für alle Daten, die über das Netzwerk eingehen.
Interrupts in Python
In Python werden Interrupts durch die signal
Library zur Verfügung gestellt. Die dort definierten Signale sind größtenteils kompatibel zu denen, die unter Linux benutzt werden. Diese können wir uns einfach mit kill -l
anzeigen lassen.
Signale
Ich kann hier unmöglich auf alle oben gezeigten Signale eingehen, daher beschränke ich mich auf einige häufig benutzte, denn das Verfahren ist in Python immer sehr ähnlich.
Name | Nummer | Beschreibung |
---|---|---|
SIGHUP | 1 | Ursprünglich: Verbindung zum Terminal wird beendet (HangUP), Heute: Signal zum Neuladen der Konfigurationsdateien. |
SIGINT | 2 | Programm wird mit CTRL-C beendet. |
SIGUSR1 | 10 | benutzerdefiniert. |
SIGUSR2 | 12 | benutzerdefiniert. |
SIGTERM | 15 | Programm soll sich beenden. |
SIGCONT | 18 | Programm fortsetzen |
SIGTSTP | 19 | Programm wird mit CTRL-Z in den Hintergrund gelegt |
Signale benutzen
Wie man den Programmabbruch durch den KeyboardInterrupt
als Exception abfängt, habe ich schon verwendet. In diesem Fall wird das zugehörige Signal (SIGTERM) vom Python Interpreter verarbeitet und als Exception an dein Programm weitergegeben. Du kannst aber deinen eigenen Interrupt-Handler in die Kette einklinken.
Dazu brauchst du eine Methode, die beim signalisierten Ereignis aufgerufen wird und dieses verarbeitet:
#Behandlung von SIGINT (CTRL-C)
def handle_sigint(signum, frame) :
print(f'Handling signal {signum} ({signal.Signals(signum).name}).')
if signum == signal.SIGINT:
print('SIGINT wird behandelt.')
time.sleep(1)
sys.exit(0)
Jede Signal Handler Methode erwartet zwei Parameter signum
mit der Nummer des Signals (siehe oben bei kill -l) und frame
, dieser enthält ein Objekt vom Typ signal.FrameType und enthält den „Rahmen“, in dem das Signal empfangen wurde.
Den frame
Parameter brauchst du vermutlich eher selten, den signum
eigentlich nur dann, wenn du eine gemeinsame Methode für alle Signale benutzt.Zur besseren Übersicht benutze ich hier allerdings separate Methoden für jedes Signal.
Als nächstes muss deine Methode als Signal-Handler registriert werden:
signal.signal(signal.SIGINT, handle_sigint)
Diese Zeile sagt nichts anderes aus als: Immer, wenn das Signal SIGINT
auftaucht, rufe die Methode handle_sigint()
auf. In der Beispielmethode beenden wir das Programm einfach nur. Es wäre aber möglich, dort noch eine Sicherheitsabfrage einzubauen oder Berechnungsergebnisse abzuspeichern. Ich bin allerdings der Meinung, dass so eine Methode keine langwierigen Aufgaben durchführen sollte sondern schnellstens nach der Abarbeitung des Signals wieder zum normalen Programmablauf zurückgeben sollte. Für alles andere gibt’s das Multithreading.
Das Ganze hab ich in einem Demonstrationsprogramm eingebunden:
#! /usr/bin/python3
import time
import signal
import sys
#Behandlung von SIGINT (CTRL-C)
def handle_sigint(signum, frame) :
print(f'Handling signal {signum} ({signal.Signals(signum).name}).')
if signum == signal.SIGINT:
print(f'SIGINT wird behandelt. {frame}')
time.sleep(1)
sys.exit(0)
#Behandlung von SIGTSTP (CTRL-Z)
def handle_sigtstp(signum,frame):
print(f'Behandle signal {signum} ({signal.Signals(signum).name}).')
print('Programm in Hintergrund')
# Behandlung von SiGCONT
def handle_sigcont(signum,frame):
print(f'Behandle signal {signum} ({signal.Signals(signum).name}).')
print('Programm im Vordergrund')
if __name__ == '__main__':
# Interrupt Handler registrieren
signal.signal(signal.SIGINT, handle_sigint)
signal.signal(signal.SIGTSTP, handle_sigtstp)
signal.signal(signal.SIGCONT, handle_sigcont)
for i in range(0,10000000):
print(f'Schleife: {i}')
time.sleep(0.5)
print('Schleifenende')
Das Programm registriert neben SIGINT auch noch Handler für SIGCONT und SIGTSTP und gibt in einer Schleife nur die ersten 10 Mio.
Integerzahlen aus. Wenn du während des Programablaufs CTRL-C drückst, passiert folgendes:
Wie du siehst, wird nach dem CTRL-C unsere Handler-Methode aufgerufen. Im Frameobjekt steht neben dem Namen des Pythonfiles auch die Zeilennummer, die zum Zeitpunkt des Signals aktuell war.
zeitgesteuerte Signale
Mit SIGALRM
steht dir ein Signal zu Verfügung, mit dem du dein Programm nach einer definierten Zeit unterbrechen lassen kannst.
#! /usr/bin/python3
# encoding:utf-8
import signal
import time
def handle_alarm(signum, frame):
print(f'Alarm ausgelöst bei {time.ctime()}')
signal.signal(signal.SIGALRM,handle_alarm)
signal.alarm(3)
print(f'aktuelle Zeit Start: {time.ctime()}')
time.sleep(13)
print(f'aktuelle Zeit Ende: {time.ctime()}')
Die Handler-Methode handle_alarm()
wird wie oben beschrieben registriert und danach wird mit signal.alarm(3) die Zeit in Sekunden gesetzt, nach der der Alarm Interrupt ausgelöst werden soll. Das Programm selbst tut nichts anderes als 13 Sekunden warten. Die Ausgabe sieht dann so aus:
Das war unser kurzer Ausflug in die Welt der Interrupts und Signale. Im Repository ist der Programmcode hinterlegt.
Im nächsten Teil schauen wir uns NumPy mal an.