Performance beim Threading

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.
microhome
User
Beiträge: 7
Registriert: Montag 9. März 2009, 18:58

Performance beim Threading

Beitragvon microhome » Montag 9. März 2009, 19:06

Hallo liebe Python-Gemeinde!
Ich bin Pythonanfänger und habe mich jetzt mit dem Thema Threading beschäftigt. Ich möchte ein Script schreiben, welches jede halbe Sekunde auf eine MySQL Datenbank zugreift und schaut, ob ein neuer Eintrag in einer Tabelle dazugekommen ist. Sollte das der Fall sein, wird ein neuer Thread gestartet und eine entsprechende Prozedur ausgeführt.

Das halbsekündliche Prüfen habe ich momentan über eine einfache "while True" Schleife realisiert. Diese Funktion selbst läuft auch in einem Thread.

Nun würde ich gern wissen, ob diese Überprüfung ein Performanceproblem ist. Sollte dem so sein, würde ich gern wissen, ob und wie das "ununterbrochene Ausführen einer Funktion" performancesparender programmiert werden kann?!


Beste Grüße und vielen Dank für eure Hilfe :-)
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Beitragvon würmchen » Montag 9. März 2009, 19:19

hast du ein wenig Beispielcode?

Ich denke es kommt drauf an, wie du wartest und wenn Du der Meinung bist, dass so oft geprüft werden muss, dann ist das schon mal kein Parameter an dem man Performance einsparen kann.

ich denke wenn du mit dem sleep aus dem time Modul arbeitest sollte der schlafende Thread recht wenig Rechenleistung in Anspruch nehmen :-)
sea-live
User
Beiträge: 440
Registriert: Montag 18. Februar 2008, 12:24
Wohnort: RP

Beitragvon sea-live » Montag 9. März 2009, 19:26

servus und willkommen im forum
das kanst du zb so lösen

Code: Alles auswählen

#!/usr/bin/env python
import wx
import time
import wx.lib.newevent
#wx.SetDefaultPyEncoding("iso-8859-15")
# Event-Klasse und Eventbinder-Funktionen erstellen
(NewStatusEvent, EVT_NEW_STATUS) = wx.lib.newevent.NewEvent()
class MyEventTimer(wx.Timer):
    def __init__(self):
        wx.Timer.__init__(self)
        self.Start(1000)
        # Es wird jetzt jede Sekunde der Timer ausgelöst. Dabei wird
        # jedes Mal die Methode "Notify" aufgerufen. In dieser Notify-Methode
        # wird eine Event-Instanz erstellt und ausgelöst.
    def Notify(self):
        """
        Eine Methode mit dem Namen "Notify" wird bei jedem Timer-Event
        aufgerufen, ohne dass man diese an ein Event binden muss.
        Dient nur zum Simulieren von ARBEIT! ;-)
        """
        # Event-Instanz erstellen
        evt = NewStatusEvent()
        # Aktuelle Zeit an die Event-Instanz binden
        evt.current_time = time.asctime()
        # Event auslösen
        self.ProcessEvent(evt)

class Frame(wx.Frame):
    def __init__(
            self, parent = None, ID = 1, title = u"Threading Gen", pos=wx.DefaultPosition,
            size=(300,200), style= wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)
            ):

        wx.Frame.__init__(self, parent, ID, title, pos, size, style)
        self.panel = wx.Panel(self, -1)
        self.panel.SetBackgroundColour((0,205,205))

        # Event der **MyEventTimer-Instanz** an Handler binden
        myeventtimer = wx.GetApp().myeventtimer
        myeventtimer.Bind(EVT_NEW_STATUS, self.update_status)
        self.lab_status = wx.StaticText(self.panel,pos=(3,150))
    def update_status(self, event):
        """
        Dieser Eventhandler bekommt ein Event übergeben. An dieses Event
        wurde die aktuelle Zeit als Attribut ``current_time`` angehängt.
        Diese wird jetzt im StaticText-Feld angezeigt.
        """
        self.lab_status.SetLabel(event.current_time)

class MyApp(wx.PySimpleApp):
   
    def OnInit(self):
       
        self.myeventtimer = MyEventTimer()
        # Ab jetzt sendet self.myeventtimer jede Sekunde ein Event
        # bis die Anwendung abgebrochen wird. Gleichzeitig ist die Instanz
        # auch an die App gebunden. So kann auch vom Frame aus darauf zugegriffen
        # werden.
       
        # Frame anzeigen
        fenster = Frame()
        fenster.Center()
        fenster.Show()
        return True

def main():
    app = MyApp()
    app.MainLoop()

if __name__ == "__main__":
    main()
BlackJack

Beitragvon BlackJack » Montag 9. März 2009, 21:31

@sea-live: Wo ist denn im ersten Beitrag bitte von `wx` oder überhaupt GUIs die rede!?
microhome
User
Beiträge: 7
Registriert: Montag 9. März 2009, 18:58

Beitragvon microhome » Dienstag 10. März 2009, 12:27

Servus und danke für die vielen Antworten.
Mein Code sieht aber um einiges "einfacher" aus:

Code: Alles auswählen

def spiderdb():
    ts = str(int(time.time()))
    cursor.execute("""SELECT id, start, artikel, userid FROM orders WHERE status LIKE 0""")
    cursor.fetchall()
    for row in cursor:
        dbid = row[0]
        dbts = str(row[1])
        artikel = str(row[2])
        userid = str(row[3])
        if dbts == ts:
            cursor.execute("""UPDATE orders SET status = 1 WHERE id LIKE %d""" %dbid)
            # Hier wird ein neuer Thread gestartet, sobald ein Timestamp gefunden wurde
            t_spiderurl = Thread(target = spiderurl, args = (artikel, userid, dbid))
            t_spiderurl.start()

def gospiderdb():
    while True:
        time.sleep(0.5)
        spiderdb()


Gestartet wird das Ganze dann mit

Code: Alles auswählen

t_gospiderdb = Thread(target = gospiderdb, args = ())
t_gospiderdb.start()



Ich denke, dass es wesentlich performancesparender nicht geht, oder?
Und ich habe noch eine kleine Frage: Ihr seht ja, dass bei einer bestimmten Bedingung ein neuer Thread erzeugt wird. Gibt es in Python eine Möglichkeit, alle aktiven Threads anzuzeigen??


Vielen Dank und beste Grüße!
Benutzeravatar
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Beitragvon ms4py » Dienstag 10. März 2009, 13:26

threading.enumerate()
Return a list of all Thread objects currently alive. The list includes daemonic threads, dummy thread objects created by current_thread(), and the main thread. It excludes terminated threads and threads that have not yet been started.


Die Timeout-Funktion von einem GUI-Toolkit ist vermutlich schon performanter, wie das Beispiel von sea-live.
Hier noch ein kurzes Beispiel in GTK ohne GUI Elemente:

Code: Alles auswählen

#!/usr/bin/env python

import time

import gtk
import gobject


def timeout():
   print time.time()
   return True
   
gobject.timeout_add(500, timeout)
gtk.main()
Benutzeravatar
kbr
User
Beiträge: 806
Registriert: Mittwoch 15. Oktober 2008, 09:27
Wohnort: Düsseldorf

Beitragvon kbr » Dienstag 10. März 2009, 13:39

microhome hat geschrieben:Ich denke, dass es wesentlich performancesparender nicht geht, oder?

Unterstützt MySQL Trigger? Das scheint mir das zu sein, was Du wirklich suchst.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Beitragvon rayo » Dienstag 10. März 2009, 13:41

Also:
  • SQL Befehle besser machen
    LIKE sollte wenn möglich vermieden werden
    keine Werte direkt in den SQL String einfügen
    Nich über das ganze Resultat iterieren und nochmals einen Wert abfragen -> direkt in Query nehmen
  • Threads nicht übermässig erstellen, lieber 2 Threads die dauernd laufen und miteinander kommunizieren (z.B. mit einer Queue)

Also nach den Änderungen siehts etwa so aus (natürlich ungetestet):

Code: Alles auswählen

def spider_url_thread(input_queue):
    while True:
        artikel, userid, dbid = input_queue.get()
        print 'tu was damit'

def spider_db_thread(output_queue):
    while True:
        ts = int(time.time())
        cursor.execute("""SELECT id, artikel, userid FROM orders WHERE status = 0 AND start = ?""", (ts, ))
        #cursor.fetchall() # brauchts das wirklich? gehts nicht direkt mit dem cursor?
        for row in cursor:
            dbid = row[0]
            artikel = str(row[2])
            userid = str(row[3])
           
            cursor.execute("""UPDATE orders SET status = 1 WHERE id = ?""", (dbid,))
            output_queue.put((artikel, userid, dbid))
       
        time.sleep(0.5)

q = Queue.Queue()
t1 = Thread(target=spider_db_thread, args=(q,))
t2 = Thread(target=spider_url_thread, args=(q,))

t1.start()
t2.start()


Und falls die Daten in der DB nicht so schnell ändern kannst du anstatt Polling (pro Sekunde 2 mal abfragen ob eine Zeit erreich wurde) auch einfach den nächsten Timestamp auslesen (eine Query mit LIMIT 1 und ODER BY start ASC) und solange den Thread schlafen legen entweder per time.sleep(start-jetzt) oder mit einem threading.Timer() die Funktion aufrufen.

*edit* wie kommt man eigentlich darauf, dass timeouts von GUI Toolkits seien besser als ein einfacher sleep? Beim sleep wird nicht wirklich was gemacht :)

*edit 2*
Die nicht pollende Funktion würde vielleicht so aussehen wobei ich bei den DB-Zugriffen nicht wirklich sicher bin (fetch one), hab da schon lange nichts mehr damit gemacht.

Code: Alles auswählen

def spider_db_thread(output_queue):
    while True:
        cursor.execute("""SELECT id, start, artikel, userid FROM orders WHERE status = 0 ORDER BY start ASC LIMIT 1""")
        now = int(time.time())
        row = cursor.fetchone()
       
        # berechne Zeit bis start erreicht wird
        diff = int(row[1])-now
        # schlafe bis start erreicht wurde
        time.sleep(diff)
       
        dbid = row[0]
        artikel = str(row[2])
        userid = str(row[3])
       
        cursor.execute("""UPDATE orders SET status = 1 WHERE id = ?""", (dbid,))
        output_queue.put((artikel, userid, dbid))
Zuletzt geändert von rayo am Dienstag 10. März 2009, 13:48, insgesamt 1-mal geändert.
EyDu
User
Beiträge: 4868
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Beitragvon EyDu » Dienstag 10. März 2009, 13:43

An Stelle von "sleep" würde ich wohl ein Event verwenden. Wenn man möchte, kann man dann eine Überprüfung auch von außen starten. Einen Performance-Unterschied macht dies aber ganz sicher nicht.

ice2k3 hat geschrieben:Die Timeout-Funktion von einem GUI-Toolkit ist vermutlich schon performanter, wie das Beispiel von sea-live.


Und deine Vermutung gründet auf was?

Versuch mal: http://dev.mysql.com/doc/refman/5.1/de/create-trigger.html
Das Leben ist wie ein Tennisball.
microhome
User
Beiträge: 7
Registriert: Montag 9. März 2009, 18:58

Beitragvon microhome » Dienstag 10. März 2009, 16:24

Ich werde das ganze mal testen!
Habe nun aber noch eine Frage zu einem Thread. Sobald ein entsprechender Timestamp erreicht ist, öffne ich mit Python eine in der Datenbank hinterlegte URL in einem neuen Thread und logge mich ein. Dies ist nötig, um eine bestimmte Aktion auszuführen. Der Thread läuft nach dem Ausführen der Aktion weiter und prüft die Seite weiterhin. Sobald ein entsprechendes Ereignis eintritt, soll diese Aktion wieder ausgeführt werden. Wenn ich mich dann allerdings wieder einlogge und die Seite für die Aktion aufrufe, erhalte ich folgenden Fehler: "HTTPError: HTTP Error 307: Temporary redirect"

Ich vermute, dass der Login fehlschlägt, da der Thread ja noch eingeloggt ist. Wie kann ich mit mechanize prüfen, ob ich bereits eingeloggt bin?

Hier mein Code:

Code: Alles auswählen

import urllib, mechanize

def login(user, password):
    loginurl = "http://www.seite.de/login.html"
    logindata = urllib.urlencode(
        {
            "login" : user,
            "password" : password
        }
    )
    response = mechanize.urlopen(loginurl, logindata)
    return response.read()

print login ('friedolin', 'blubber') # gibt den Inhalt der Seite zurück
print "____________________________________________________________"
print login ('friedolin', 'blubber') # gibt o.g. Fehlercode aus
sea-live
User
Beiträge: 440
Registriert: Montag 18. Februar 2008, 12:24
Wohnort: RP

Beitragvon sea-live » Dienstag 10. März 2009, 16:37

wenn du logich forgehst dann gibt ja login was zurück
und du kanst in login mit ner if abfrage feststellen ob response diesen wert hat oder nicht
wenn login korrekt dann kein erneuter login
beim logout response löschen
microhome
User
Beiträge: 7
Registriert: Montag 9. März 2009, 18:58

Beitragvon microhome » Dienstag 10. März 2009, 16:46

Ja, wie gesagt, es gibt mir den Quelltext der gesamten Seite zurück. Aber moment, ich kann ja in jedem Thread eine Variable setzen. Sobald ich mich das erste Mal einlogge, wird diese verändert. Wenn die Variable nun geändert ist, brauch der Thread sich nicht mehr einloggen, sondern kann direkt die URL für die Veränderung aufrufen! Ich teste mal :-)

Wer ist online?

Mitglieder in diesem Forum: Astorek