Seite 1 von 1
threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 15:15
von zybork
Hallo Leute!
Folgendes quälendes Problem: Ich will einen Thread mit einem Signal (wir reden von Linux hier) unterbrechen, wieso funktioniert folgender Code nicht?
Code: Alles auswählen
#!/usr/bin/python
# -*- coding: utf-8 -*-
import threading
import time
import os
import sys
import signal
print os.getpid()
time.sleep(2)
Running = True
class Process(threading.Thread):
def __init__(self):
self.running = True
threading.Thread.__init__(self)
def run(self):
for iter in range(10):
print iter
if not Running:
print "I break now"
break
time.sleep(1)
def sighup(signal, frame):
Running = False
print " ** sighup **"
signal.signal(signal.SIGHUP, sighup)
my = Process()
my.start()
for iter in ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"]:
print iter
time.sleep(1.4)
Beim Ausführen sieht man, dass der Signal-Handler aufgerufen wird, aber offenbar funzt das mit der globalen Variablen nicht. Wie macht man sowas? Ich habe nach eineinhalb Stunden im Guugl bzw. Herumexperimentieren nicht herausgefunden, wie es geht.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 17:29
von pillmuncher
Hallo & Willkommen im Forum.
Üblich sind 4 spaces pro Einrückungsebene, nicht zwei. Mehr dazu in
PEP8.
iter ist der Name einer eingebauten Funktion. Wenn du eine Variable so benennst, verdeckst du diese eingebaute Funktion in deinem scope.
Globale Variablen funktionieren in Python anders als in den meisten anderen Sprachen. Erstens sind sie nur global in Bezug auf das Modul, in dem sie definiert sind, zweitens kann man sie in Funktionen zwar lesen, aber sobald man ein Zuweisung an sie im Code stehen hat, interpretiert Python das als Definition einer lokalen Variablen, die die globale verdeckt. Das ist kein Bug, sondern ein Feature. Um dem zu entgehen muss man die globale Variable im scope der Funktion als global deklarieren:
Code: Alles auswählen
my_var = 'hallo'
def my_func():
global my_var
my_var = 'jetzt neu!'
my_func()
assert my_var == 'jetzt neu!'
Weil das alles so clumsy ist, meiden erfahrene Pythonistas globale Variablen wie der sprichwörtliche Teufel das Weihwasser. Statt dessen würden wir eher sowas empfehlen:
Code: Alles auswählen
#!/usr/bin/python
# -*- coding: utf-8 -*-
import threading
import time
import os
import signal
print os.getpid()
time.sleep(2)
class Process(threading.Thread):
def run(self):
self.cancelled = False
for each in range(10):
print each
if self.cancelled:
print "I break now"
break
time.sleep(1)
def sighup(self, signal, frame):
self.cancelled = True
print " ** sighup **"
my = Process()
signal.signal(signal.SIGHUP, my.sighup)
my.start()
for word in ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"]:
print word
time.sleep(1.4)
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 17:30
von anogayales
So wie es jetzt aussieht, willst du den Thread beenden bevor er überhaupt ausgeführt wird. Sowas kann nicht funktionieren.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 17:35
von pillmuncher
@anogayales: Nein, das stimmt schon so.
signal.signal sendet kein signal, sondern istalliert einen signal handler.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 21:01
von anogayales
Und wo wird dieses Signal ausgelöst? Im ursprünglichen Quelltext seh ich nämlich davon nix

Vielleicht lern ich ja was dazu
Grüße,
anogayales
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 21:16
von BlackJack
@anogayales: Das Signal kommt von aussen, vom Betriebssystem. Zum Beispiel wenn das Programm über eine Remoteverbindung gestartet wurde und die Gegenseite die Verbindung schliesst. HUP ist die Abkürzung für HangUP. Kommt noch aus Zeiten wo man Modems über die Telefonleitung benutzte und das Modem quasi den „Hörer” aufgelegt hat.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 21:32
von pillmuncher
@anogayales: Was BlackJack gesagt hat. Du kannst auch, wenn du schnell bist, sowas machen: Zwei Shell-Fenster nebeneinander öffnen. Im ersten:
Ich habe die bash verwendet. Die Zahl ist die process id. Damit kannst du im zweiten Fenster dieses tun:
Die weitere Ausgabe im ersten Fenster sieht dann ungefähr so aus:
Code: Alles auswählen
alpha
0
1
beta
2
gamma
3
4
delta
5
** sighup **
epsilon
6
I break now
zeta
eta
theta
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Freitag 4. Mai 2012, 21:38
von anogayales
Vielen herzlichen Dank! Wieder was dazu gelernt!
Grüße,
anogayales
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Samstag 5. Mai 2012, 00:54
von jerch
Wobei ich mir nicht sicher bin, ob das Signalhandling dergestalt (über Methode des Subthreads) wirklich "threadsafe" ist oder nicht zu Nebeneffekten führen kann. Zumindest sagt die Doku zu signals, das nur der Hauptthread die signals erhält. Funktioniert Dein Bsp. denn in dieser Form?
Alternativ müsste man den Hauptthread mit einem entsprechenden Handler austatten und den Subthread dann per Event beenden.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Samstag 5. Mai 2012, 03:30
von pillmuncher
@jerch: Ich hab es mal folgendermaßen angepasst:
Code: Alles auswählen
...
class Process(threading.Thread):
def run(self):
print 'sub: %s' % threading.current_thread()
self.cancelled = False
for each in range(10):
print each
if self.cancelled:
print "I break now"
break
time.sleep(1)
def sighup(self, signal, frame):
print 'sighup: %s' % threading.current_thread()
self.cancelled = True
print " ** sighup **"
print 'main: %s' % threading.current_thread()
my = Process()
signal.signal(signal.SIGHUP, my.sighup)
my.start()
...
Ergebnis:
Code: Alles auswählen
$ python threadsignal.py
6032
main: <_MainThread(MainThread, started 536870968)>
sub: <Process(Thread-1, started 536945680)>alpha
0
sighup: <_MainThread(MainThread, started 536870968)>
** sighup **
beta
1
I break now
gamma
delta
epsilon
zeta
eta
theta
Der signal handler läuft also im Hauptthread. Die handler Methode ist zwar eine Methode des Process-Objekts (also einer Instanz einer von threading.Thread abgeleiteten Klasse), aber dieses ist ja nicht selbst der Thread, sondern repräsentiert ihn nur. Auf dieser Repräsentation kann man von von jedem beliebigen Thread aus jede Methode aufrufen, und die aufgerufene Methode läuft dann im aufrufenden Thread. Laut Doku garantiert Python, dass Signale immer im Hauptthread landen, weswegen auch nur dort ein signal handler installiert werden darf. Ob durch den Aufruf des signal handlers eine race condition entsteht, lässt sich damit genauso einfach/schwierig feststellen, wie in dem Fall, wo man die signal handler Funktion selbst aus dem Hautthread heraus aufruft. Im vorliegenden Fall könnte es passieren, dass der signal handler aufgerufen wird, bevor die run-Methode gestartet wurde. Dabei würde
cancelled zuerst auf
True und danach auf
False gesetzt, es wäre also so, als ob das Signal nie ausgelöst worden wäre. Deswegen würde ich im vorliegenden Fall die Aufrufreihenfolge im Hauptthread ändern und ein Latch verwenden:
Code: Alles auswählen
...
class Latch(object):
def __init__(self):
self._cond = threading.Condition(threading.Lock())
self._flag = False
def set(self):
with self._cond:
if self._flag:
raise RuntimeError('Latch has already been set!')
self._flag = True
self._cond.notifyAll()
def await(self):
with self._cond:
while not self._flag:
self._cond.wait()
class Process(threading.Thread):
def __init__(self, latch):
self.latch = latch
threading.Thread.__init__(self)
def run(self):
self.cancelled = False
self.latch.set() # <== hier
for each in range(10):
print each
if self.cancelled:
print "I break now"
break
time.sleep(1)
def sighup(self, signal, frame):
self.cancelled = True
print " ** sighup **"
latch = Latch()
my = Process(latch)
my.start()
latch.await() # <== und hier
signal.signal(signal.SIGHUP, my.sighup)
...
Damit ist garantiert, dass der signal handler frühestens aufgerufen werden kann, nachdem
cancelled mit
False initialisiert wurde.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Samstag 5. Mai 2012, 08:44
von zybork
Pillmuncher:
> Üblich sind 4 spaces pro Einrückungsebene, nicht zwei. Mehr dazu in PEP8.
Darüber könnten wir jetzt einen Religionskrieg führen

Zwei Leerzeichen (der „Double-Tap“ der Leertaste) haben in mancher Hinsicht gravierende Vorteile, aber wie gesagt, dazu vielleicht einmal ein eigener Thread.
Ich habe übrigens auch schon lokale Variablen verwendet gehabt, ohne, dass es funktioniert hätte, weshalb ich überhaupt auf die Idee mit der globalen Variablen verfallen bin.
Wenn ich das jetzt allerdings wieder so mach, wie vorher, sprich, self.running=True in __init__ definieren, funktioniert es jetzt, und zwar ganz egal, ob ich diese Zuweisung im __init__ der von threading.Thread abgeleiteten Klasse
vor threading.Thread.__init__(self) setze oder
nachher. Ich habe also entweder einen der merkwürdigsten Bugs aller Zeiten auf dem Hals gehabt, oder aber in all den Versuchen vorher irgendeinen kleinen, unscheinbaren, aber ausschlaggebenden Fehler gemacht.
Das Schlüsselwort global ist mir bekannt, ich glaube, ich habe schlicht im Laufe der Zeit keine Konzentrationsfähigkeit mehr gehabt, wie auch immer, dass iter ein Builtin ist, war mir nicht bekannt, danke für die Information.
...
Jetzt funktioniert es auch, wenn ich signal.signal mit den entsprechenden Parametern aus __init__ heraus aufrufe. Ich frage mich wirklich, wo
(bevor ich die globale Variable genommen habe) mein Fehler gelegen ist. Sei's drum, danke für die Hilfe.
PS: Offensichtlich ist es nicht üblich, hier gelöste Probleme mit „SOLVED“ zu markieren.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Samstag 5. Mai 2012, 09:20
von BlackJack
@zybork: Wenn Du bei einer Sprache wo Einrückung zur Semantik gehört und man nur unkompliziert zusammen arbeiten kann, wenn dabei alle Beteiligten der gleichen Konvention folgen, einen Religionskrieg führen möchtest, dann bring wenigstens bessere Argumente als das man durch mehrfaches drücken der Leertaste bei zwei Leerzeichen weniger Arbeit hat. Ich kenne echt *keinen* Programmierer der mit der Leertaste einrückt. Das macht entweder der Editor an den Stellen wo er das erkennen kann von alleine oder man benutzt zum Einrücken einer Ebene *einen* Druck auf die Tabulatortaste. Und zum Ausrücken einer Ebene bei den meisten Editoren die Umschalttaste + Tabulatortaste.
Re: threading.Thread mit signal.signal unterbrechen, wie?
Verfasst: Samstag 5. Mai 2012, 11:07
von zybork
aber wie gesagt, dazu vielleicht einmal ein eigener Thread.
Okay, diskutier ich gern aus, aber – du bist eh ein Moder., du kannst wohl einen neuen Thread draus machen – ein eigener Thread wär hier wohl angebracht, weil keine Relevanz zum ursprünglichen Thema.