Gui, Tkinter, Threads, Queue

Code-Stücke können hier veröffentlicht werden.
Antworten
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Gerade weil es in letzter Zeit mehrere Postings zu diesem Thema gab,
möchte ich das Ganze nochmal aufgreifen.

Das folgende Programm erzeugt eine grössere Anzahl
von Threads und kommuniziert mit der Gui mittels
eines Message-Queues.

Code: Alles auswählen

#!/usr/bin/env python

####

import Tkinter as tk
import threading 
import Queue as qu
import random as rand
import time as ti
import sys

####

class Gui(object):
    
  def __init__(self, root, controller):

    self.root = root
    self.queue = controller.queue
    self.controller = controller
    self.text = tk.Text(root)
    self.text.pack(expand=tk.YES, fill=tk.BOTH)
    root.protocol("WM_DELETE_WINDOW", self.quit)

    self.button = tk.Button(text='Stop',
                            command=self.controller.set_stop)
    self.button.pack()

    self.button = tk.Button(text='Quit', command=self.quit)
    self.button.pack()


  def quit(self):

    self.controller.set_stop()   
    self.root.destroy()
    sys.exit(0)

    
  def write(self, txt):

    self.text.insert(tk.END, txt + '\n')
    self.text.see(tk.END)


  def process(self):

    while self.queue.qsize():
      try:
        msg = self.queue.get()
        self.queue.task_done()
        self.write(str(msg))
        self.root.update_idletasks()
      except qu.Empty:
        pass

    self.root.after(1, self.process)

####
    
class Threader(object):

  def __init__(self, root, n, sleeptime=0.5):

    self.root = root
    self.queue = qu.Queue(maxsize=1000)
    self.gui = Gui(root, self)

    self.set_running()
    self.threads = [None] * n
    for i in xrange(n):
      self.threads[i] = threading.Thread(target=self.worker,
                                         args=(i, sleeptime))
      self.threads[i].setDaemon(1)
      self.threads[i].start()

    self.set_running()
        

  def set_running(self):

    self.running = 1
    self.gui.process()

    
  def set_stop(self):

    self.running = 0

    
  def worker(self, i, sleeptime):

    while self.running:
      t = rand.random() * sleeptime
      ti.sleep(t)
      if self.running:
        msg = 'thread: %3d - slept: %s - queue size: %s' % \
              (i, str(t), self.queue.qsize())
        self.queue.put(msg)
        self.queue.join()
   
####

def main_threader():

  # zum rumspielen
  MAX_THREADS = 380
  SLEEPTIME = 0.4
  
  root = tk.Tk()
  root.title('Queue Polling')
  Threader(root, MAX_THREADS, SLEEPTIME)
  root.mainloop()

####

if __name__ == '__main__':

  main_threader()

####
Ist es so (und *nur* so) richtig?

:wink:
yipyip
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Wenn ich vom Verhalten des Programms ausgehe, würde ich sagen, es funktioniert NICHT:

Nach dem Start läuft das Fenster mit den Thread-Angaben, die Buttons reagieren aber nicht, weil der Ablauf die gesamte "Energie" an sich bindet - die Events kommen nicht mehr durch.

Außerdem werden auf der Konsole haufenweise Meldungen ausgespuckt:
"Original exception was:
Unhandled exception in thread started by
Error in sys.excepthook:"
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Bei mir bleibt noch ein klein wenig Energie für die Buttons
übrig, so das diese noch reagieren.

Der Wert '380' ist bei mir die maximale Anzahl der
verfügbarten Threads, danach gibts ein

'thread.error: can't start new thread'

Haengt dieser Wert also vom Betriebssystem und nicht von Python ab?
Bei einer wesentlich geringeren Anzahl von Threads sollte es aber
funktionieren?
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

yipyip hat geschrieben:Bei einer wesentlich geringeren Anzahl von Threads sollte es aber funktionieren?
Ja, stimmt! Hatte ich vorher nicht probiert.

Bei mir liegt die kritische Zahl etwa bei 300. Bis 320 muss man einige Klickversuche machen, dann klappt es meist noch. Bis etwa 300 Threads reagieren die Buttons sofort.

Es liegt aber nicht nur an der Threadzahl, sondern auch an der "Sleeptime". Wenn ich diese höher setze, dann werden auch mehr Threads verkraftet.

Ich vermute, dass es eher an der Rechenpower als am Betriebssystem liegt (mein Rechner ist schon ziemlich betagt).

Edit: Bei längerer Sleeptime sind bei mir auch mehr als 380 Threads möglich.
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

Mir war schon bewusst, dass ich die
(#Threads, sleeptime)-Kombination ziemlich
'scharf' eingestellt habe.
Ist die sleeptime zu gering und die Anzahl der
Threads zu hoch , so bleibt wohl keine Zeit
mehr für die Mainloop, auf Events zu reagieren.

Ursprünglich ging es mir ja nur um das Konzept
mit der(dem?) Message-Queue.
Benutzt man nämlich kein Queue und lässt die Threads
jeweils direkt in die Gui schreiben, so kann die
Ausgabe ins Stocken geraten bzw. ganz einfrieren.

Nachtrag: Bei mir ist immer bei 380 Threads Schluss,
auch bei sleeptime von z.B: 20.0
(Athlon 2400, Xubuntu 8.04, Python 2.5)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

yipyip hat geschrieben:Nachtrag: Bei mir ist immer bei 380 Threads Schluss,
auch bei sleeptime von z.B: 20.0
(Athlon 2400, Xubuntu 8.04, Python 2.5)
Bei mir liegt die Grenze bei 1018 Threads - unabhängig von der sleeptime.
(OpenSuSE 10.0 mit Python 2.5)

Edit: Deine Fragen zum Konzept kann ich dir leider nicht beantworten - dafür verstehe ich zu wenig davon ... :cry:
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

1018? :shock:
Das verstehe ich nicht, woher der
Unterschied zwischen Suse und Ubuntu kommt.

Konzeptionell beziehe ich mich auf
http://effbot.org/zone/tkinter-threads.htm
(wie immer der Effbot...)

Musste mich auch erst etwas an dieses Thema gewöhnen...

:wink:
yipyip
yipyip
User
Beiträge: 418
Registriert: Samstag 12. Juli 2008, 01:18

2 Korrekturen muss ich zum obigen Programmcode anbringen.

1: Da ist ein "self.set_running()" Aufruf zuviel.
(:oops: ...)

2: Wenn man permanent in das Text-Widget schreibt, sollte
man es auch hin und wieder loeschen.
(Kann ja keiner ahnen, dass da das Tkinter immer
etwas Speicher abknabbert...)

Hier die korrigierte Version:
http://paste.pocoo.org/show/93017/

Ein Speicherleck kann ich aber sonst nicht entdecken.

:wink:
yipyip
Antworten