Heisenbug?

Fragen zu Tkinter.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Alfons Mittelmeyer hat geschrieben: Und was willst Du mit Deinem Code Ausschnitt sagen:
...
Dass es abhängig von Compilerflags ist, welches Scheduling verwendet wird? Ja, wie der Compiler aufgerufen wird, steht wohl nicht im C-Code. Da müßtest Du schon auf das Makefile verweisen.
Ok, ich schreib das jetzt mal ganz langsam, weil du das ja offensichtlich etwas schwierig verstehst und darum ganz langsam lesen musst:

Das, lieber Alfons, ist ein Python Modul mit dem Namen "posix". Wenn das Betriebssytem SCHED_FIFO kennt, dann hat auch dieses Modul eine Konstante mit gleichem Namen. Damit *kann* man dann einen Aufruf machen, der die Thread-Prioritaet auf SCHED_FIFO setzt.

Aber nur weil diese Konstante im Code definiert wird heisst das noch lange nicht, dass sie auch benutzt wird! Es wird ja auch zB die Funktion shutil.rmtree definiert, mit der man sich seine gaaaaanze Festplatte loeschen koennte. Beim Start von Python. Tut man aber nicht, weil es niemand aufruft.

Und das gleiche gilt fuer SCHED_FIFO (bzw sched_param, das es benutzt) - es wird nirgendwo aufgerufen! Ausser in einem Test.

Langer Rede, kurzer Sinn: Es. Wird. Nicht. Aufgerufen. Und. Man. Muesste. ROOT. Sein. Damit. Es. Klappt. Was. Man. Aber. Ueblicherweise. Nicht. Ist.

Klar geworden? "Kapiert", wie du so schoen immer fragst?

Alfons Mittelmeyer hat geschrieben: Es ist doch völlig egal mit was Du wartest, ob mit sleep oder etwas anderem. Hauptsache Du wartest mit etwas, anstatt mit Endlosschleife zu laufen, dann kommen nämlich auch die anderen Threads dran.
Nochmal sehr langsam: auch bei deinem urspruenglichen Programm mit busy-waits OHNE sleep oder einem anderen Call kam der GUI-Thread "dran". Kannst ja mal ein print in poll_queue stopfen. Wird schoen regelmaessig aufgerufen. Das duerfte es laut deiner abstrusen Idee nicht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Lieber das Schwadron auf volle Kanne, und losspekuliert.
Tja, wer da wild drauf losspekuliert, dürfte klar sein. Man hat den Code da, man kann in den Code ein print setzen und sich etwas ausgeben lassen. Dann weiß man, dass da kein Overload der Queue da ist, man kann auch das Reinschieben in die cQueue mit 1/50 swekunden also after(20,..) vergleichen mit after(1,..) beim Rausholen und zudem noch das Rausholen durch Schleife bis die Queue leer ist. Dann weiß man genau, dass da nichts zuviel in die Queue gelegt wird.

Dann hat man völlig falsche Vorstellungen von Mehrkernprozessoren in Verbindung mit Threads. Auf Kernelebene ist eine Aufteilung auf mehrere Prozessoren von POSIX Threads durchaus möglich.

Kannst es ja mal mit der Kithara Realtime Suite probieren, mit der ich entwickelt hatte:

http://kithara.com/de/loesungen/echtzeit-timer#tasks

Auf User Ebene geht da aber gar nichts. Ja mit FIFO hast Du recht. Dann ist es eben SCHED_OTHER, also diese Threads laufen, bis sie blockieren oder die Kontrolle abgeben (yield durch sleep und dergleichen, event in einem anderen Thread setzen ... ) oder durch einen Thread höherer Priorität unterbrochen werden. Jedenfalls ist es nicht SCHED_RR, bei dem es die Zeitscheibe gibt. Wenn man nicht blockiert oder die Kontrolle abgibt oder Python umwechselt (könnte etwa nach 100 Ticks durch event setzen geschehen, aber hab mir das nicht angeschaut) dann bleibt man im selben Thread.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:
Alfons Mittelmeyer hat geschrieben: Es ist doch völlig egal mit was Du wartest, ob mit sleep oder etwas anderem. Hauptsache Du wartest mit etwas, anstatt mit Endlosschleife zu laufen, dann kommen nämlich auch die anderen Threads dran.
Nochmal sehr langsam: auch bei deinem urspruenglichen Programm mit busy-waits OHNE sleep oder einem anderen Call kam der GUI-Thread "dran". Kannst ja mal ein print in poll_queue stopfen. Wird schoen regelmaessig aufgerufen. Das duerfte es laut deiner abstrusen Idee nicht.
Was ist busy-waits? Das war Poll auf Zeit Schleife ohne sleep. Und natürlich war da der GUI Thread dran. Da wurde ja auch mit after ein Event getriggert, wodurch der Thread von waiting auf running übergeht und dann auch gleich aufgerufen wird.

Das after und das Abholen von der Queue funktioniert einwandfrei. Wie lange schreibe ich das schon!!! Aber danach bekommt der GUI Thread die Kontrolle nicht mehr, weil der oder die andern Threads keinen Threadwechsel zulassen, außer den durch Python Zeitscheibe, die aber anscheinend nur laufenden Python Code berücksichtigt und keine C-Code Ebene für tkinter intern.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Um dem Geschwafel hier etwas konkretes gegenüberzustellen: Es ist tatsächlich so, dass der tkinter-Thread im Thread-Scheduler zu kurz kommt; meine Vermutung warum, hatte ich schon gepostet. Mag der Thread-Scheduler Default-Einstellungen haben, so lassen sich diese seit Python 3.2 mit der neuen Funktion sys.setswitchinterval() bezüglich der Laufzeit beeinflussen (das ältere sys.setcheckinterval wird nicht mehr unterstützt). Dann ist auch kein sleep() mehr erforderlich, um zu signalisieren, dass ein potentieller blocking-call erfolgt und der Thread-Scheduler vorzeitig aktiv werden kann.

Code: Alles auswählen

import tkinter as tk

import threading
import sys
from queue import Queue, Empty
from time import time, sleep

transfer = Queue()


class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.text = tk.Text(self)
        self.text.pack()
        self.poll_queue()

    def poll_queue(self):
        while True:
            sys.setswitchinterval(0.2)  # give tkinter time
            try:
                output = transfer.get_nowait() + '\n'
            except Empty:
                break
            else:
                self.text.insert(tk.END,output)
        self.after(100, self.poll_queue)


def thread1():
    while True:
        zeit = time()
        while time()-zeit < 1:
            sys.setswitchinterval(0.0001)  # don't run mad without switching
            pass
        transfer.put('1')


def thread2():
    while True:
        zeit = time()
        while time()-zeit < 1:
            sys.setswitchinterval(0.0001)  # dito
            pass
        transfer.put('2')


t1 = threading.Thread(target=thread1)
t1.start()

t2 = threading.Thread(target=thread2)
t2.start()

Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ob es dein Bug ist, darüber kann man streiten. Wer in C programmiert, sollte wissen, dass da ein wait erforderlich ist. In Python ist das ein sleep. Damit gibt es auch kein Problem. Dass Python auch nach Zeitscheibe wechselt ist eine zusätzlich Userunterstützung, wenn er sleep nicht macht. Diese Userunterstützung ist aber nur halbherzig implementiert, weil sie nur Threads mit laufendem Python Code berücksichtigt und andere Threads, die auf Nichtpython Ebene auf Ausführung warten, nicht.

Tja, Bug oder nicht Bug, das ist hier die Frage. Jedenfalls ist es falsch, sleep nicht zu benutzen, auch wenn es richtig implementiert wäre, weil das die Performance herunterdrückt.

Auch drückt ein anderer Thread die Performance herunter wenn man in diesem sleep verwendet. sleep(0) heißt nicht, dass man ganz schnell wieder dran ist, sondern heißt, dass man dann wieder dran ist, wenn der andere Thread, der auch etwas zu tun hat, fertig ist. Und das ist bei der GUI eventuell ein neuer Bildaufbau, bevor man mit sleep(0) wieder dran ist. Besser ist jedenfalls selber Thread und triggern mit after, da das ein Event ist und Priorität hat gegenüber einem neuen Bildaufbau. Der Bildaufbau kann das eventuell noch berücksichtigen und wenn nicht, dann eben das nächste Mal. Jedenfalls kann man schon einmal die neuen Werte für denselben oder den nächsten Bildaufbau ohne Stocken übermitteln.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

kbr hat geschrieben:Um dem Geschwafel hier etwas konkretes gegenüberzustellen
Danke kbr, wenn es nur Geschwafel wäre, es sind auch Rechthaberei und Angriffe, ohne Ahnung zu haben aber zu denken, dass man ein Experte sei, der alles weiß.

Besser als pollen, finde ich einen Timer. Am Besten finde ich einen Event, weil ein solcher Priorität hat. Ob allerdings ein event.wait auch Priorität hat, weiß ich jetzt nicht. Statt pollen sollte man am Besten gleich die Zeit angeben. Hier wäre das mit event.wait:

Code: Alles auswählen

import tkinter as tk
 
import threading
import sys
from queue import Queue, Empty
from time import time, sleep
 
transfer = Queue()
event = threading.Event()
 
class Application(tk.Tk):
 
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.text = tk.Text(self)
        self.text.pack()
        self.poll_queue()
 
    def poll_queue(self):
        while True:
            sys.setswitchinterval(0.2)  # give tkinter time
            try:
                output = transfer.get_nowait() + '\n'
            except Empty:
                break
            else:
                self.text.insert(tk.END,output)
        self.after(100, self.poll_queue)
 
 
def thread1():
    while True:
        event.wait(1)
        transfer.put('1')
 
 
def thread2():
    while True:
        event.wait(1)
        transfer.put('2')
 
 
t1 = threading.Thread(target=thread1)
t1.start()
 
t2 = threading.Thread(target=thread2)
t2.start()
 
Application().mainloop()
Man könnte den Event aber auch über den after timer des GUI Threads triggern (event.set). Ob da dann die Performance noch besser würde? Die Frage wäre, kommt event.wait vorne in die Queue, weil es ein Event ist, oder hinten weil es ein wait ist.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Die Lösung, die mir am Besten gefällt, ist diese:

Code: Alles auswählen

import tkinter as tk
 
import threading
import sys
from queue import Queue, Empty
from time import time, sleep
 
transfer = Queue()
event1 = threading.Event()
event2 = threading.Event()
 
class Application(tk.Tk):
 
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.text = tk.Text(self)
        self.text.pack()
        self.poll_queue()
        self.trigger_events()
 
    def poll_queue(self):
        while True:
            try:
                output = transfer.get_nowait() + '\n'
                self.text.insert(tk.END,output)
            except Empty:
                break
        self.after(100, self.poll_queue)


    def trigger_events(self):
        self.after(1000,self.trigger_events)
        event1.set()
        event2.set()
 
 
def thread1():
    while True:
        event1.wait()
        event1.clear()
        transfer.put('1')

def thread2():
    while True:
        event2.wait()
        event2.clear()
        transfer.put('2')
 
t1 = threading.Thread(target=thread1)
t1.start()
 
t2 = threading.Thread(target=thread2)
t2.start()
 
Application().mainloop()
After triggert gleich das nächste after, sodass man die Codeausführungszeiten nach event.set nicht einberechnen muss. Und da das events sind, sollten sie gleich drankommen und nicht hinten in der Queue warten, bis sie an der Reihe sind.

Ach in der vorigen Variante, bei der poll_qeue von kbr stammte war in poll_qeue wegen try - except - else ein Fehler drin, der Daten verschluckte. Jetzt stimmt es wieder.
BlackJack

Ein `wait()` in C? Auf welchen Prozess? Und warum?

Threads die auf Nicht-Python-Ebene auf Ausführung warten sind nicht vom GIL betroffen, müssen also auch nicht warten bis die ”Zeitscheibe” mit Python-Code abgelaufen ist, die können tatsächlich gleichzeitig laufen wenn sie vom System Prozessorzeit auf einem anderen Kern zugewiesen bekommen. Sollten sie das GIL nicht freigegeben haben, dann ist deren Aufgabe so kurz, das es keinen Unterschied macht ob man gerade in C oder Python-Bytecode hängt (was ja letztlich auch C-Code ist der den Bytecode ausführt), oder es ist ein Programmierfehler.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:Ein `wait()` in C? Auf welchen Prozess? Und warum?

Threads die auf Nicht-Python-Ebene auf Ausführung warten sind nicht vom GIL betroffen, müssen also auch nicht warten bis die ”Zeitscheibe” mit Python-Code abgelaufen ist, die können tatsächlich gleichzeitig laufen wenn sie vom System Prozessorzeit auf einem anderen Kern zugewiesen bekommen.
Vielleicht noch nicht mitbekommen, dass es sich bei Python nicht um ein Programm auf Kernelebene handelt, mit dem man Threads auf andere Prozessoren legen kann, sondern um ein Programm auf User Ebene, das in einem Prozess läuft und bei dem die Threads im selben Prozess laufen.

Auch mitkommen, dass die Threads mit scheduling SCHED_OTHER ausgeführt werden?

Und was gilt für SCHED_OTHER?

schedpolicy SCHED_OTHER
New thread uses Solaris-defined fixed priority
scheduling; threads run until preempted by a higher-priority thread or until they block or yield.


Siehe: https://users.cs.cf.ac.uk/Dave.Marshall/C/node30.html

Also andere Threads kommen nicht dran, bevor der augenblickliche Thread nicht blockiert oder die Kontrolle abgibt. Das hat nichts mit GIL zu tun, aber mit GIL hat das etwas zu tun, dass Python, sofern es sich um Python Code handelt, einen Threadwechsel auch ohne dass auf Python Code Ebene die Kontrolle abgegeben wird, vornimmt. Nicht Python Threads bekommen sie dadurch aber leider nicht.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Gerade war es noch SCHED_FIFO, jetzt ist's SCHED_OTHER - aber die Besserwisser sind immer die anderen. Is klar..

Und auch hier liegst du wieder mit voller Wucht daneben. Natürlich unterbricht Linux auch Threads die nicht blockieren oder freiwillig Zeit abgeben. Solaris mag das anders handhaben, darüber reden wir hier aber nicht. Bestenfalls zu Linux noch macOS, und Windows, und alle drei lassen Threads nur für eine gewisse Zeit laufen, dann kommen andere dran.

http://man7.org/linux/man-pages/man7/sched.7.html

Und Tkinter läuft zusammen mit Python in genau EINEM thread. Dem Main Thread. Da gibts nicht nochmal irgendeinen KOntextwechsel. Auch eine deiner Fantasien.
BlackJack

@Alfons Mittelmeyer: Ich habe mitbekommen und ich weiss das Threads nicht auf einen Prozessor(kern) beschränkt sind. Sonst wären sie auch nur sehr beschränkt sinnvoll um Code zu parallelisieren, insbesondere unbrauchbar um durch parallele Ausführung von CPU-lastigem Code mehr Geschwindigkeit zu erreichen. Dafür werden Threads aber verwendet. Also kann Deine Theorie nicht stimmen.

Wenn ich in C-Code einen zusätzlichen Thread starte und in beiden Threads dann ordentlich CPU-Zeit verbrate passiert das in der Regel gleichzeitig auf zwei Kernen.

Bei Python-Bytecode kann das auf einem oder zwei Kernen passieren, aber wegen dem GIL nicht gleichzeitig. Wenn man in den Threads C-Code ausführt der das GIL freigibt, dann läuft der in der Regel parallel, auf einem Kern Python-Bytecode und auf einem anderen der C-Code.

Das mit dem preemptiven Unterbrechen gilt zusätzlich zu dem bisher gesagten wenn es mehr Prozesse/Threads als Kerne gibt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Gerade war es noch SCHED_FIFO, jetzt ist's SCHED_OTHER - aber die Besserwisser sind immer die anderen. Is klar..
Ds war nicht gerade, das ist schon lange her. Jedenfalls hatte ich klar gestellt, dass es nicht SHED_RR ist, welches diese Zeitscheibe hat.
__deets__ hat geschrieben: Und auch hier liegst du wieder mit voller Wucht daneben. Natürlich unterbricht Linux auch Threads die nicht blockieren oder freiwillig Zeit abgeben. Solaris mag das anders handhaben, darüber reden wir hier aber nicht. Bestenfalls zu Linux noch macOS, und Windows, und alle drei lassen Threads nur für eine gewisse Zeit laufen, dann kommen andere dran.

http://man7.org/linux/man-pages/man7/sched.7.html
Und wo liest Du da raus, dass Linux den Thread unterbricht?
SCHED_OTHER: Default Linux time-sharing scheduling
SCHED_OTHER can be used at only static priority 0 (i.e., threads
under real-time policies always have priority over SCHED_OTHER
processes). SCHED_OTHER is the standard Linux time-sharing scheduler
that is intended for all threads that do not require the special
real-time mechanisms.

The thread to run is chosen from the static priority 0 list based on
a dynamic priority that is determined only inside this list. The
dynamic priority is based on the nice value (see below) and is
increased for each time quantum the thread is ready to run, but
denied to run by the scheduler. This ensures fair progress among all
SCHED_OTHER threads.
Da geht es nur darum, welcher Thread als nächstes drankommt. Wer am längsten warten mußte (es gibt ja Events), der wird dann bevorzugt aufgerufen, wenn es wieder um die Frage geht, wer jetzt dran ist

SCHED_RR allerdings hätte dieses maximal time Quantum:
SCHED_RR: Round-robin scheduling
SCHED_RR is a simple enhancement of SCHED_FIFO. Everything described
above for SCHED_FIFO also applies to SCHED_RR, except that each
thread is allowed to run only for a maximum time quantum. If a
SCHED_RR thread has been running for a time period equal to or longer
than the time quantum, it will be put at the end of the list for its
priority. A SCHED_RR thread that has been preempted by a higher
priority thread and subsequently resumes execution as a running
thread will complete the unexpired portion of its round-robin time
quantum. The length of the time quantum can be retrieved using
sched_rr_get_interval(2).
Wo willst Du das über SCHED_OTHER gelesen haben?
__deets__ hat geschrieben:Und Tkinter läuft zusammen mit Python in genau EINEM thread. Dem Main Thread. Da gibts nicht nochmal irgendeinen KOntextwechsel. Auch eine deiner Fantasien.
Jetzt reichts bald, ich hatte nichts darüber geschrieben dass Tkinter in einem andern Thread wie Python wäre.
Aber wenn Du einen anderen Thread mit Threading aufmachst dann hast Du einen anderen Thread und zwischen solchen Threads gibt es Kontextwechsel. Ist doch klar, oder? Oder ist etwas mit Deinen Gehirnwindungen nicht in Ordnung?

Außerdem ist es aber doch so, dass Python Code in einer anderen Umgebung ist als Python selber. Oder hast Du schon erlebt, dass Python Code nicht mehr abgearbeitet werden kann, weil jetzt in diesem Thread Python nicht mehr dran ist?
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

Leute, so hatte ich das Pythonforum aber nicht in Erinnerung, dass hier Threads für schwer verständliche Diskussionen über Technikalitäten gehijacket werden. Mögt ihr vielleicht den ganzen Kreppes abtrennen und auf meine Frage viewtopic.php?f=18&t=40957#p312752 eingehen?
PS: Die angebotene Summe ist beachtlich.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man I muss das schon richtig lesen: auch SCHED_OTHER kennt ein quantum. Und "fair progress among all...". Das gilt für alle tasks in Linux, und Threads sind in Linux fast Prozesse - außer das sie sie eben in einem Prozessraum leben. Das macht den KOntextwechsel etwas billiger, ändert aber nichts an der grundlegenden Zeitscheibenarchitektur.

Und Dinge die du in diesem Thema hier gesagt hast sind "gerade". Nicht lange. Und es ändert auch nix daran, das du im Brustton tiefster Überzeugung immer wieder Behauptungen aufstellst - wie eben das Python Threads in SCHED_FIFO packt - die nicht stimmen. Aber davon bloß nich beirren lassen, und unter Unterstellung von Inkompetenz anderer die Nächte haltlose These rauskloppen. Ein Expedde halt.

Und natürlich behauptest du hier fortgesetzt, es gäbe irgendwie eine Unterschied zwischen Python Code im Main Thread, und Tkinter. Den es nicht gibt. Das einfügen des update_idletasks führt IM MAIN THREAD in dem Python Code & Tkinter C ud TCL laufen ganz wunderbar aus. Auch wenn anderer Threads gerade eine kleine Endlosschleife losgetreten haben. Das habe ich schon vor Äonen belegt (deine Zeitrechnung)...

Und in deinem letzen Satz (den mein iPad gerade nicht kopieren mag :evil: ) stellst du schon wieder die Behauptung auf, es gäbe da irgendwelche Unterschiede. Ein für alle mal - gibt es nicht. Python kann auch keine Zeitscheiben vergeben oder sonstewas. Alle Threads kommen zum Zuge. Auch ohne Events, waits, sleeps & Co.
Zuletzt geändert von __deets__ am Dienstag 1. August 2017, 22:47, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

@ Üpsilon auch wenn die Diskussion stilistisch ätzend ist - sie ist durchaus on topic. Deine Probleme wären im Original wie auch ggf. beim zweiten Problem mit dem update_idletasks zu beheben. Denke ich jedenfalls. Versuch macht kluch.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Üpsilon hat geschrieben:Leute, so hatte ich das Pythonforum aber nicht in Erinnerung, dass hier Threads für schwer verständliche Diskussionen über Technikalitäten gehijacket werden. Mögt ihr vielleicht den ganzen Kreppes abtrennen und auf meine Frage viewtopic.php?f=18&t=40957#p312752 eingehen?
Also entweder Du setzt ein sleep(0) in Deine pollschleife rein, auch wenn das keiner wahrhaben will, oder Du nimmst das. Das bezieht sich aber auf Deinen Eingangspost und ich habe auch das seitwärts korrigiert. Allerdings einen anderen Thread hast Du dann nicht mehr, obwohl die Locks noch drin sind:

Code: Alles auswählen

#!/usr/bin/env python3
 
from canvas_screen import Screen
from random import randint, choice
from time import time
from threading import Lock, Thread
from time import sleep
 
SPIELFELD_GROESSE = 500
BALKEN_AUF_EINMAL = 5
BALKEN_BREITE = 100
BALL_RADIUS = 20
SCROLL_GESCHWINDIGKEIT = 2 # Pixel pro Schritt
SEITWAERTS_GESCHWINDIGKEIT = 10
FPS = 50
 
class Rapid_Roll:
    def __init__(self):
        # linke Ecke der Balken als Koordinatentupel
        self.balken = [(randint(0, SPIELFELD_GROESSE-BALKEN_BREITE),
                        SPIELFELD_GROESSE/BALKEN_AUF_EINMAL*i)
                       for i in range(1,BALKEN_AUF_EINMAL+1)]
        # Mittelpunkt vom Ball
        self.ball = choice(self.balken)
        self.ball = [self.ball[0]+BALKEN_BREITE/2, self.ball[1]-BALL_RADIUS]
       
        self.gedrueckt = 0 # -1 für links, +1 für rechts
        self.gedrueckt_lock = Lock()
        self.spiel_laeuft = True
        self.screen = Screen(SPIELFELD_GROESSE, SPIELFELD_GROESSE)
        self.screen.window.bind('<Button-1>', lambda event: self.on_gedrueckt(-1))
        self.screen.window.bind('<Button-3>',  lambda event: self.on_gedrueckt(1))
        self.screen.window.bind('<ButtonRelease-1>', lambda event: self.on_gedrueckt(0))
        self.screen.window.bind('<ButtonRelease-3>', lambda event: self.on_gedrueckt(0))
 

    def on_gedrueckt(self,value=None):
        self.gedrueckt_lock.acquire()
        if value != None:
            self.gedrueckt = value
        return_value = self.gedrueckt
        self.gedrueckt_lock.release()
        return return_value
 
    def schritt(self):
        # Ball seitwärts
        gedrueckt = self.on_gedrueckt()
        self.ball[0] += gedrueckt*SEITWAERTS_GESCHWINDIGKEIT
        if self.ball[0] < 0  or self.ball[0] > SPIELFELD_GROESSE:
            self.ball[0] -= gedrueckt*SEITWAERTS_GESCHWINDIGKEIT
        # Herausfinden, ob der Ball auf einem Balken liegt
        liegt_auf = any(b[0] <= self.ball[0] <= b[0]+BALKEN_BREITE and b[1]-BALL_RADIUS <= self.ball[1] <= b[1] for b in self.balken)
        # Ball hochziehen oder fallen lassen
        if liegt_auf:
            self.ball[1] -= SCROLL_GESCHWINDIGKEIT
        else:
            self.ball[1] += 2*SCROLL_GESCHWINDIGKEIT
        # Balken scrollen
        self.balken = [(x, y-SCROLL_GESCHWINDIGKEIT) for (x,y) in self.balken if y>SCROLL_GESCHWINDIGKEIT]
        while len(self.balken) <= BALKEN_AUF_EINMAL: # wenn ein Balken oben rausgerutscht ist
            self.balken.append((randint(0, SPIELFELD_GROESSE-BALKEN_BREITE),
                                self.balken[-1][1]+SPIELFELD_GROESSE/BALKEN_AUF_EINMAL))
        # prüfen ob der Ball noch im Feld ist
        if self.ball[1] < 0 or self.ball[1] > SPIELFELD_GROESSE:
           self.spiel_laeuft = False
 
    def ausgabe(self):
        for i, b in enumerate(self.balken):
            self.screen.input.put({'type':'line', 'name':i, 'bbox':(b[0], b[1], b[0]+BALKEN_BREITE, b[1])}),
        self.screen.input.put({'type':'circle', 'name':'ball', 'M':self.ball, 'r':BALL_RADIUS})
 
    '''
    def hauptschleife(self):
        zeit = time()
        while self.spiel_laeuft:
            while time()-zeit < 1/FPS:
                sleep(0)
                #pass
            zeit += 1/FPS
            self.schritt()
            self.ausgabe()
    '''

    def hauptschleife(self):
        if self.spiel_laeuft:
            self.screen.window.after(int(1000/FPS),self.hauptschleife)
        self.schritt()
        self.ausgabe()

rr = Rapid_Roll()
##Thread(target=rr.hauptschleife).start()
rr.hauptschleife()
rr.screen.mainloop()
Und die Beantwortung der Frage: wenn man kein sleep reinmacht oder eine IO Operation wie print kommt der GUI Thread nicht mehr zum Bildaufbau dran, weil dem Python Interpreter Nicht Python Code egal ist und er nicht auf den GUI Thread umschaltet, damit der den Bildaufbau auch noch erledigen kann.

Also, mit IO Operationen ist es so, wenn Du etwa ein input einfügst, dann wartet der Thread nicht etwa, bis Du Deine Eingabe gemacht hast, sondern das Betriebssystem schaltet dann sofort auf einen anderen Thread um. Erst Nach Drücken von Enter kommt dann der Thread wieder dran. Bei Print und anderen IO Operationen ist es ähnlich. Das Betriebssystem schaltet richtig um und berücksichtigt auch den GUI Thread. Python mit Endlosschleife tut das aber nicht.

Python schaltet nach einer Maximalzeit dann den Thread um aber nur, wenn es in einem anderen Thread auch auf Python Ebene etwas zu tun gibt. Und das ist dann im GUI Thread aber nicht der Fall.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Üpsilon: was ist jetzt noch Dein Problem? Die Lösung ist, wie in meinem ersten Beitrag schon zu lesen, keinen eigenen Thread zu starten. Wenn Du wissen willst, warum Deine urpsrüngliche Lösung nicht funktioniert: Python erlaubt nur einen gleichzeitig laufenden Thread. Dein busy-loop scheint aus welchen Gründen auch immer fast die gesamte Rechenzeit an sich zu reißen. Warum, ist eigentlich egal, weil der Fehler der Busy-Loop ist, den man immer durch **ein** sleep ersetzen kann.

Die restlichen Beiträge sind zum einen Teil (A.M.) reine Spekulation, bzw. versuche, das klar zu stellen. Das Niveau sind aber reine Implementierungsdetails, die morgen schon wieder anders sein könnten, also für einfache Programme nicht relevant.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Man I muss das schon richtig lesen: auch SCHED_OTHER kennt ein quantum. Und "fair progress among all..."..
The thread to run is chosen from the static priority 0 list based on
a dynamic priority that is determined only inside this list. The
dynamic priority is based on the nice value (see below) and is
increased for each time quantum the thread is ready to run, but
denied to run by the scheduler. This ensures fair progress among all
SCHED_OTHER threads.
Gelesen hast Du es ja vielleicht richtig, verstanden hast Du es offensichtlich nicht, Es gibt eine dynamische Priorität - die man auch als nice value bezeichnet. Und jedes Zeit Quantum, wo der Thread bereit wäre zum Starten - er also in der Queue der startbereiten Threads steht, er aber dann vom Scheduler doch nicht gestartet wird - das kann z.B. sein, dass ein anderer Tread noch vor ihm dran ist, oder dass ein Event für einen anderen Thread getriggert wird, dannn wird seine Priorität erhöht - nice value Wert.

Und was bedeutet das? Wenn ein anderer Thread blockiert - etwa auch durch sleep, dann hat er, wenn er lange warten mußte, eine höhere Priorität und kommt dann eher dran, wie andere, die nicht solange warten mußten. Und das ist der faire Prozess.

Das hat mit dem Zeitquantum wie beim SCHED_RR Thread, bei dem der Thread nach einem maximalen Zeit Quantum unterbrochen wird, überhaupt nichts zu tun.

Wenn man Quantum liest und wieder Quantum liest, dann kann das auch ein ganz anderes Quantum sein, wie etwa ein Quantum Bier zuviel.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Dein busy-loop scheint aus welchen Gründen auch immer fast die gesamte Rechenzeit an sich zu reißen. Warum, ist eigentlich egal, weil der Fehler der Busy-Loop ist, den man immer durch **ein** sleep ersetzen kann.

Die restlichen Beiträge sind zum einen Teil (A.M.) reine Spekulation, bzw. versuche, das klar zu stellen. Das Niveau sind aber reine Implementierungsdetails, die morgen schon wieder anders sein könnten, also für einfache Programme nicht relevant.
Das sind nicht welche Gründe auch immer. Der sleep ist völlig richtig. und Spekulationen von mir sind das schon gar nicht, denn das sind die Fakten:
schedpolicy SCHED_OTHER
New thread uses Solaris-defined fixed priority
scheduling; threads run until preempted by a higher-priority thread or until they block or yield.
Es ist fast die ganze Rechenzeit, Nur durch das Pollen mit after kommt der GUI Thread dran und schafft es nebenher nach langer Zeit auch noch einen Bildaufbau zu machen, was man aber vergessen kann. Ohne sleep kommt nicht das Betriebssystem dran, welches den Taskwechsel vollzieht, bei dem tkinter den Bildaufbau machen kann. Besser als sleep ist aber keine eigene Task sondern ein after, denn sonst erfolgt während dem Bildaufbau kein Taskwechsel, und dann sind eventuiell die 1/FPS nicht einzuhalten. Ein after würde ein Event auslösen, das Vorrang vor Fertigstellung des Bildaufbaus hat.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Üpsilon: wenn es aber unbedingt doch ein anderer Thread sein soll, kann man vom GUI Thread aus auch den Übergang in den anderen Thread forcieren, wenn man nämlich die 1/FPS einhalten will und nicht warten will, bis tkinter ganz mit dem Bildaufbau fertig ist.

Diesen Übergang kann man nämlich durch triggern mit after und event.set() auslösen:

Code: Alles auswählen

#!/usr/bin/env python3
 
from canvas_screen import Screen
from random import randint, choice
from time import time
from threading import Lock, Thread, Event
from time import sleep
 
SPIELFELD_GROESSE = 500
BALKEN_AUF_EINMAL = 5
BALKEN_BREITE = 100
BALL_RADIUS = 20
SCROLL_GESCHWINDIGKEIT = 2 # Pixel pro Schritt
SEITWAERTS_GESCHWINDIGKEIT = 10
FPS = 50
 
class Rapid_Roll:
    def __init__(self):
        # linke Ecke der Balken als Koordinatentupel
        self.balken = [(randint(0, SPIELFELD_GROESSE-BALKEN_BREITE),
                        SPIELFELD_GROESSE/BALKEN_AUF_EINMAL*i)
                       for i in range(1,BALKEN_AUF_EINMAL+1)]
        # Mittelpunkt vom Ball
        self.ball = choice(self.balken)
        self.ball = [self.ball[0]+BALKEN_BREITE/2, self.ball[1]-BALL_RADIUS]
       
        self.gedrueckt = 0 # -1 für links, +1 für rechts
        self.gedrueckt_lock = Lock()
        self.spiel_laeuft = True
        self.screen = Screen(SPIELFELD_GROESSE, SPIELFELD_GROESSE)
        self.screen.window.bind('<Button-1>', lambda event: self.on_gedrueckt(-1))
        self.screen.window.bind('<Button-3>',  lambda event: self.on_gedrueckt(1))
        self.screen.window.bind('<ButtonRelease-1>', lambda event: self.on_gedrueckt(0))
        self.screen.window.bind('<ButtonRelease-3>', lambda event: self.on_gedrueckt(0))
        self.event = Event() 

    def on_gedrueckt(self,value=None):
        self.gedrueckt_lock.acquire()
        if value != None:
            self.gedrueckt = value
        return_value = self.gedrueckt
        self.gedrueckt_lock.release()
        return return_value
 
    def schritt(self):
        # Ball seitwärts
        gedrueckt = self.on_gedrueckt()
        self.ball[0] += gedrueckt*SEITWAERTS_GESCHWINDIGKEIT
        if self.ball[0] < 0  or self.ball[0] > SPIELFELD_GROESSE:
            self.ball[0] -= gedrueckt*SEITWAERTS_GESCHWINDIGKEIT
        # Herausfinden, ob der Ball auf einem Balken liegt
        liegt_auf = any(b[0] <= self.ball[0] <= b[0]+BALKEN_BREITE and b[1]-BALL_RADIUS <= self.ball[1] <= b[1] for b in self.balken)
        # Ball hochziehen oder fallen lassen
        if liegt_auf:
            self.ball[1] -= SCROLL_GESCHWINDIGKEIT
        else:
            self.ball[1] += 2*SCROLL_GESCHWINDIGKEIT
        # Balken scrollen
        self.balken = [(x, y-SCROLL_GESCHWINDIGKEIT) for (x,y) in self.balken if y>SCROLL_GESCHWINDIGKEIT]
        while len(self.balken) <= BALKEN_AUF_EINMAL: # wenn ein Balken oben rausgerutscht ist
            self.balken.append((randint(0, SPIELFELD_GROESSE-BALKEN_BREITE),
                                self.balken[-1][1]+SPIELFELD_GROESSE/BALKEN_AUF_EINMAL))
        # prüfen ob der Ball noch im Feld ist
        if self.ball[1] < 0 or self.ball[1] > SPIELFELD_GROESSE:
           self.spiel_laeuft = False
 
    def ausgabe(self):
        for i, b in enumerate(self.balken):
            self.screen.input.put({'type':'line', 'name':i, 'bbox':(b[0], b[1], b[0]+BALKEN_BREITE, b[1])}),
        self.screen.input.put({'type':'circle', 'name':'ball', 'M':self.ball, 'r':BALL_RADIUS})
 

    def hauptschleife(self):
        while True:
            self.event.wait()
            self.event.clear()
            if self.spiel_laeuft:
                self.schritt()
                self.ausgabe()
            else:
                break

def trigger_event():
   rr.screen.window.after(int(1000/FPS),trigger_event)
   rr.event.set()

rr = Rapid_Roll()
Thread(target=rr.hauptschleife).start()
trigger_event()
rr.screen.mainloop()
Antworten