queue+threads

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Hi,

Ich lese mich gerade in queues ein. Dabei ist mir aufgefallen,dass in Verbindung mit Threads (fast) immer eine Endlosschleife verwendet wird:

https://pymotw.com/2/Queue/

Eigentlich wäre das logischer für mich:

Code: Alles auswählen

while not q.empty():
Bei der Endlosschleife würde ich davon ausgehen,dass irgendwann die empty exception geworfen wird.

Vielleicht kann mit das jemand erklären :)

Gruß Frank
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Es wird keine Exception geworfen, weil get so lange blockiert, bis wieder was in der Queue ist. Sinn solcher Threads ist es, das sie Aufgaben abarbeiten, die z.b. von Nutzern der GUI ausgelöst werden.
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Danke dir, habe das mit der python doku [1] nachvollziehen können (block=True).

D.h. der thread läuft die ganze Zeit,bis der Prozess (Anwendung) beendet wird. Ein daemon=True wäre hier fatal,richtig?

Kann man den Thread dann irgendwie beenden? Muss vermutlich in der klasse eine Abbruchbedingung setzen,welche innerhalb der "while true" schleife geprüft wird (setzt natürlich vorraus,dass die position noch erreicht wird,da evtl.bei get hängt)...ein thread.stop habe ich noch nicht gefunden.evtl.ist der sauberste Weg,die "Abbruchbedingung" auch in die queue zu schicken :) dann wird get getriggert und kann die Schleife verlassen und den thread beenden.

[1] https://docs.python.org/3/library/queue.html
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w: Warum sollte ein ``daemon=True`` fatal sein? Das sollte man eigentlich fast immer machen. Es gibt eher wenige Fälle wo man das nicht machen möchte, weil in den meisten Fällen es einfach nur nervig ist wenn Prozesse von aussen gekillt werden müssen weil das Hauptprogramm zwar eigentlich beendet werden sollte, es aber Threads gibt, die das Ende verhindern.

Abbruchbedingung über die Queue wenn da noch irgendwie auf den Abbruch reagiert werden soll, bevor es tatsächlich abbricht. Ansonsten reicht ``daemon=True`` und beenden des Hauptprogramms.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Daemon=true sorgt ja dafür (wenn ich es richtig verstehe) dass der thread weiterläuft,wenn das hauptprogramm beendet wird,bis der Thread fertig ist. Wenn ich while true mache und queue.get auf weitere daten wartet,läuft der Thread doch ewig weiter,oder nicht? Also grob das Szenario,was du ansprichst...

Oder wird die queue dann zerstört und damit der Thread beendet?
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w Du hast `daemon` genau falsch herum verstanden. Wenn man das auf `True` setzt, dann läuft der Thread gerade *nicht* weiter wenn das Hauptprogramm beendet wird, sondern wird mit dem Hauptprogramm beendet.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

__blackjack__ hat natuerlich recht, ich finde den Begriff und die Semantik aber tatsaechlich auch verwirrend. Ein Daemon laeuft an sich ja unabhaengig von anderen Systemteilen, aber hier dann nicht, sondern er wird eben gestoppt. Wohingegen er munter weiterlaeuft, wenn daemon=False... finde ich ungluecklich.
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Danke euch...habe es wirklich andersherum (vom losgelösten prozess unter linux) verstanden...die doku ist da ein bisschen verwirrend (doppelte Verneinung)

Ich habe es jetzt so (self.workthread wird im init der Klasse auf None gesetzt):

Code: Alles auswählen

    def worker(self):
        while True:
            item = self.q.get()
            self.scrolltxt.insert(tk.END, str(item)+ '\n')
            self.q.task_done()
            sleep(1)

    def createthread(self):
        if not self.workthread or not self.workthread.is_alive():
            self.workthread=Thread(target=self.worker,daemon=True)
            self.workthread.start()

    def clickme(self,num=0):
        c1=num
        c2=num+30
        for item in range(c1,c2):
            self.q.put(item)

        self.createthread()
Ist natürlich exemplarisch...es füllt die queue einmal vor dem start des threads und beim zweiten klick ist der thread noch aktiv (wird nicht nochmal gestartet) und da wird nur die queue gefüllt
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Wie kann ich den thread als gelöst kennzeichnen? Habs bisher nicht gefunden...
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@frank-w,

Das gibt's auch nicht. Hier wird nur diskutiert und nichts gelöst :)
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w: Der Code wäre ein bisschen einfacher wenn Du den Thread schon in der `__init__()` erstellst und startest. Solange der in `get()` hängt, verbraucht der keine Rechenzeit.

Edit: Argh: Gerade gesehen: Der Thread ändert was in der GUI — das ist falsch. Die darfst Du nur von dem Thread aus verändern, in dem die GUI-Hauptschleife läuft.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Das Ändern in der gui vom thread aus habe ich grob aus einem Beispiel (buch)...wie bekommt die gui mit,dass der thread was gemacht hat,ohne zu blockieren? Über eine 2.queue (get würde halt die gui blockieren,genauso wie eine schleife zum pollen dieser)?

Betreffend der gelöst-Kennzeichnung habe ich die grünen Haken bei den anderen Themen gesehen und wollte mich dran halten,da es in anderen Foren zum "guten Ton" gehört
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w: Man pollt üblicherweise eine Queue mit einer Methode und `Widget.after()`.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Eine bekannte Suchmaschine hat das zu Tage gefördert:

https://stackoverflow.com/q/49370592

Wenn ich pause_and_empty nach dem Füllen der queue aufrufe, müsste das passen,oder? Brauche ich das pause oder reicht das Abfragen,ob die queue leer ist? self.parent muss dann vermutlich der button sein,der die queue gefüllt hat,oder?
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w: `self.parent` muss *irgendein* `Widget` sein. Welche ist egal. Diese `pause_and_empty()`-Methode brauchst Du IMHO überhaupt nicht. Der Code da ist komisch und unübersichtlich und die Frage ist mit -1 bewertet und es gibt auch keine richtige Antwort.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Kannst du mir vielleicht ein besseres beispiel geben? Der sinn an der funktion ist ja die rekursion...also wenn bei einer prüfung noch was in der queue drin ist,wird nochmal gewartet.

Kann auch das Fenster selbst verwendet werden?

Hier wird das scheinbar gemacht:

https://www.oreilly.com/library/view/py ... 09s07.html

Auch wenn ich das mit dem sys.exit nicht ganz nachvollziehen kann...self.running wird ja nur in endApplication auf 0 gesetzt,welches nie aufgerufen wird
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ja das mit dem `exit()` ist schräg, da würde man einfach per `quit()` auf dem passenden Objekt die `mainloop()` verlassen.

Es wird nie gewartet, also nicht in `Queue.get()`. Und es wird immer `after()` aufgerufen. Es sei denn Du definierst irgendeinen Wert den der/die Threads an das Hauptprogramm/die GUI senden können/dürfen, der das Programm beenden soll.

Rekursion ist da übrigens keine. Die Methode ruft `after()` auf und kehrt zur Hauptschleife zurück. Das *die* dann später wieder die Methode aufruft ist keine Rekursion. Dafür müsste die Methode gleichzeitig mehrfach aktiv sein.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
frank-w
User
Beiträge: 21
Registriert: Freitag 19. Februar 2021, 18:38

Habs jetzt mit einer 2.queue (Rückrichtung als Erledigungsmeldung) und ohne exit/quit (macht aus meiner sicht keinen Sinn,da die job-queue mehrmals gefüllt werden kann) gemacht.

Code: Alles auswählen

    def syncGUI(self):
        while not self.doneq.empty():
            item=self.doneq.get()
            self.scrolltxt.insert(tk.END, str(item)+ '\n')
            self.doneq.task_done()
            if self.q.empty():
                self.scrolltxt.insert(tk.END, 'no more jobs\n')
            self.scrolltxt.see(tk.END)
        self.win.after(200,self.syncGUI)
q ist die job-queue welche der thread abarbeitet,doneq ist die queue für die Rückmeldung.

Warum darf der thread die gui nicht direkt updaten? Hat auch funktioniert...könnte evtl. bei mehreren threads Probleme bereiten bei gleichzeitigem Zugriff.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das hat funktioniert wie es auch funktioniert, mit geschlossenen Augen über die Straße zu gehen. Ohne vorher zu schauen, ob ein Auto kommt. Man stirbt nicht jedes Mal. Aber man *kann* sterben.

Die Innereien der GUI sind nicht davor geschützt, von mehreren Threads gleichzeitig bearbeitet zu werden. Dadurch können dann inkonsistente Zustände entstehen, die zu Abstürzen führen *können*. Nicht müssen. So wie man eben nicht zwangsläufig umkommt, wenn man ohne zu schauen auf die Straße rennt.
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@frank-w: Ich würde das ohne `empty()` machen, mit einer ``while True:``-Schleife die dann verlassen wird wenn das `get_nowait()` eine Ausnahme auslöst weil die Queue tatsächlich leer ist.

Methodennamen übrigens klein_mit_unterstrichen. 🙂
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten