2009-07-30 15 views
0

Ich benutze Python, um eine Hardware-USB-Sniffer-Gerät mit der Python-API des Anbieters Schnittstelle und ich versuche, (USB-Pakete) von dem Gerät in einem separaten Thread in einer Endlosschleife (was gut funktioniert). Das Problem ist, dass meine Hauptschleife anscheinend nie wieder eingeplant wird (meine Leseschleife bekommt die ganze Aufmerksamkeit).Scheduling-Probleme in Python

Der Code sieht ähnlich wie folgt aus:

from threading import Thread 
import time 
usb_device = 0 

def usb_dump(usb_device): 
    while True: 
     #time.sleep(0.001) 
     packet = ReadUSBDevice(usb_device) 
     print "packet pid: %s" % packet.pid 

class DumpThread(Thread): 
    def run(self): 
     usb_dump() 

usb_device = OpenUSBDevice() 
t = DumpThread() 
t.start() 
print "Sleep 1" 
time.sleep(1) 
print "End" 
CloseUSBDevice(usb_device) 
sys.exit(0) 

(Ich konnte eigentlichen Code einfügen, aber da Sie die Hardware-Gerät benötigen dar, dass ich es nicht viel helfen).

Ich erwarte, dass dieser Code beginnt, usb-Pakete für etwa eine Sekunde zu entladen, bevor der Haupt-Thread das gesamte Programm beendet. Alles, was ich sehe, ist "Sleep 1" und dann läuft die usb_dump() Prozedur für immer. Wenn ich die "time.sleep (0.001)" -Anweisung in der inneren Schleife der usb_dump()-Prozedur auskommentiere, fangen die Dinge an zu arbeiten, wie ich es erwarte, aber dann wird der Python-Code nicht mehr mit allen eingehenden Paketen mithalten können :-(

Der Verkäufer sagt mir, dass dies ein Python-Scheduler Problem ist, und ihre api nicht Schuld und dafür wird mir nicht helfen.

«Allerdings scheint es, wie Sie einige Nuancen erleben, wenn sie in Python threading von Wenn Sie den time.sleep in den DumpThread-Thread setzen, signalisieren Sie dem Python-Threading-System explizit, die Kontrolle aufzugeben, andernfalls ist es der Python-Interpreter, zu bestimmen, wann ein Thread gewechselt werden soll, und normalerweise nach einem bestimmten Anzahl der Byte-Code-Anweisungen wurden ausgeführt. »

Kann jemand bestätigen, dass Python das Problem hier ist? Gibt es eine andere Möglichkeit, die DumpThread-Versionskontrolle zu machen? Irgendwelche anderen Ideen?

Antwort

3

Ihr Lieferant würde Recht haben, wenn Ihr reinen Python Code war; C-Erweiterungen können jedoch GIL freigeben und ermöglichen daher echtes Multithreading.

Insbesondere time.sleep tut die GIL freigeben (Sie können es direkt überprüfen aus dem Quellcode, here - siehe floatsleep Implementierung); also sollte dein Code kein Problem haben. Als weiterer Beweis, ich habe auch einen einfachen Test, nur gemacht, um die Anrufe zu USB entfernen, und es funktioniert tatsächlich wie erwartet:

from threading import Thread 
import time 
import sys 

usb_device = 0 

def usb_dump(): 
    for i in range(100): 
     time.sleep(0.001) 
     print "dumping usb" 

class DumpThread(Thread): 
    def run(self): 
     usb_dump() 

t = DumpThread() 
t.start() 
print "Sleep 1" 
time.sleep(1) 
print "End" 
sys.exit(0) 

schließlich nur ein paar Noten auf dem Code, den Sie geschrieben:

  • usb_device wird nicht an den Thread übergeben. Sie müssen es als Parameter übergeben oder (argh!) Dem Thread mitteilen, dass es vom globalen Namespace abgerufen werden soll.
  • Anstatt sys.exit() zu erzwingen, könnte es besser sein, dem Thread nur zu signalisieren, dass er stoppt und dann das USB-Gerät schließt. Ich vermute, dass Ihr Code ein Multithreading-Problem bekommen könnte, wie es jetzt ist.
  • Wenn Sie nur eine regelmäßige Umfrage benötigen, kann threading.Timer-Klasse eine bessere Lösung für Sie sein.

[aktualisiert] über den letzten Punkt: wie im Kommentar gesagt, ich glaube, ein Timer besser die semantischen Ihre Funktion (eine periodische Umfrage) und würde automatisch vermeiden Probleme mit der GIL nicht wird freigegeben passen würde nach dem Verkäufercode.

+0

Auf Ihre Notizen: Ich weiß, dass der Code hässlich aussieht. Ich begann mit einem Beispielcode vom Anbieter und machte so wenig Änderungen wie möglich, während ich immer noch meinen Standpunkt zeigte. Ich dachte, sie würden ihr eigenes Beispiel kennen ... Das ist überhaupt kein Produktionscode. –

+0

Ok, wenn ich also annehme, dass die C-Erweiterung (die hinter dem ReadUSBDevice-Aufruf liegt) die GIL nicht freigibt (wie es sollte), dann fügt ein kleiner Schlaf (wie ich es versucht habe) den Code wie erwartet funktionieren, weil jetzt Ich gebe die GIL bei jeder Iteration explizit frei. Das würde das Verhalten erklären, das ich sehe. Gibt es eine andere Möglichkeit, die GIL freizugeben? Vielleicht kann ich ein wenn alle 50 Runden von etwas schläft? –

+0

Es liegt in der Verantwortung Ihres Anbieters, die GIL freizugeben, und Sie können nicht viel dagegen tun. Was ist mit einem threading.Timer-Objekt? Es würde automatisch die Semantik Ihrer Funktion (periodische Umfrage) implementieren und Sie müssten sich nicht um deren Implementierung kümmern. –

0

Ich denke, der Anbieter ist richtig. Angenommen, dies ist CPython, gibt es kein echtes paralleles Threading. Es kann immer nur ein Thread ausgeführt werden. Dies liegt an der Implementierung der global interpreter lock.

Sie können möglicherweise eine akzeptable Lösung erreichen, indem Sie das Modul multiprocessing verwenden, das die Sperre des Garbage Collector effektiv umgeht, indem echte Teilprozesse erzeugt werden.

Eine andere Möglichkeit, die helfen kann, ist die switching behaviour des Schedulers zu ändern.

2

Ich nehme an, dass Sie ein Python C-Modul geschrieben haben, das die ReadUSBDevice-Funktion verfügbar macht und blockiert werden soll, bis ein USB-Paket empfangen wird, und dann zurückgeben.

Die native ReadUSBDevice-Implementierung muss die Python-GIL freigeben, während sie auf ein USB-Paket wartet, und sie dann erneut abrufen, wenn sie eine empfängt. Dadurch können andere Python-Threads ausgeführt werden, während Sie nativen Code ausführen.

http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock

Während Sie die GIL entsperrt haben, können Sie nicht Python zugreifen. Lassen Sie die GIL los, führen Sie die Blockierungsfunktion aus, und wenn Sie wissen, dass Sie etwas zurück zu Python haben, müssen Sie es erneut erwerben.

Wenn Sie dies nicht tun, können keine anderen Python-Threads ausgeführt werden, während Ihre native Blockierung aktiv ist. Wenn es sich um ein von einem Anbieter bereitgestelltes Python-Modul handelt, ist das Fehlschlagen der Freigabe der GIL während der nativen Blockierungsaktivität ein Fehler.

Beachten Sie, dass andere Threads weiterhin ausgeführt werden sollten, wenn Sie viele Pakete empfangen und sie tatsächlich in Python verarbeiten. Mehrere Threads, die tatsächlich Python-Code ausführen, werden nicht parallel ausgeführt, wechseln jedoch häufig zwischen Threads, sodass alle ausgeführt werden können. Dies funktioniert nicht, wenn systemeigener Code blockiert, ohne die GIL zu veröffentlichen.

edit: Ich sehe, Sie erwähnten, dies ist eine vom Hersteller zur Verfügung gestellte Bibliothek. Wenn Sie keine Quelle haben, können Sie schnell sehen, ob sie die GIL freigeben: Starten Sie den ReadUSBDevice-Thread, während keine USB-Aktivität stattfindet, also sitzt ReadUSBDevice einfach herum und wartet auf Daten. Wenn sie die GIL freigeben, sollten die anderen Threads ungehindert laufen. Wenn nicht, wird der gesamte Interpreter blockiert. Das wäre ein ernsthafter Fehler.

+0

Ich habe vergessen zu erwähnen: Das ReadUSBDevice ist ein Python-c-Modul (wie Sie erwartet) und es hat eine (Standard) 500ms Timeout, so dass es auf Pakete für eine halbe Sekunde wartet und dann zurückkehrt. Ich vermute, dass es für jede Iteration die Python GIL (was immer das ist, wird darüber lesen) veröffentlicht. Da ich jedoch eine Endlosschleife verwende, startet der Thread einfach in einer anderen Schleife. Python sollte immer noch Hauptschleife für jede Iteration planen können, richtig? –

+0

Es sollte in der Lage sein, alle anderen Python-Threads während des gesamten 500ms-Timeouts auszuführen, solange es die GIL wie gewünscht veröffentlicht. Wenn es nicht ist, würde ich erwarten, dass es * schließlich * austritt - aber möglicherweise erst nach Dutzenden von Iterationen über die 500-ms-Zeitüberschreitung. –

+0

Sortieren von ungerade, dass Sie eine Antwort markiert, die eine Stunde später mit weniger Informationen als dieser als die Lösung kam. –