Getimte Ausführung - 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.
Antworten
dschoni
User
Beiträge: 2
Registriert: Dienstag 11. September 2012, 15:34

Hallo liebe Pythongemeinde.
Ich hab ein (meiner Meinung nach) sehr interessantes Problem und würde Hilfe brauchen.
Es geht darum, zwei Programmteile parallel laufen zu haben, die aber nur über einen Port (seriell) kommunizieren können.
Programmteil "A" soll in regelmäßigen Zeitabständen ausgelöst werden und einen String an die serielle Schnittstelle schicken
und idealerweise noch die Antwort lesen.
Programmteil "B" soll währenddessen auf Usereingaben warten, die ebenfalls an die Schnittstelle geschickt werden können.
Funktioniert das mit threading?
Da bin ich absoluter Neuling aber ich denke, dass man pro Aufgabe einen Thread erstellt, der sich um die jeweilige Aufgabe kümmert
(soweit richtig? oder muss man zwei komplett verschiedene Klassen dafür definieren?)
Da die serielle Schnittstelle blockiert wird von einem Thread hab ich mich auch schon in die lock-thematik eingelesen nur wie schaut das dann mit dem Zeitgeber aus?
Also wie verhindere ich, dass die Zeit von Programmteil "A" sich um die Ausführungszeit von Teil "B" nach hinten verschiebt?
Gerne auch Vorschläge das anders zu lösen (Events?...)
Dschoni.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Prinzipiell würde man so etwas mit drei Threads lösen. Einen für Aufgabe A, einen für Aufgabe B und den dritten für die serielle Schnittstelle. A und B können dann ihre Aufgaben über eine Queue an die Kommunikation schicken, welche diese dann schrittweise abarbeitet und an die Threads antwortet. Du wirst sicher noch auf viele Details stoßen, aber zumindest hast du so schon mal ein flexibles Grundgerüst.

Am meisten Probleme wird dir sicher die serielle Schnittstelle beschaffen, da du hier stark von den Protokollen abhängst: Timeouts, Zuordnung von Antworten, Schreiben und Lesen. Das kannst aber nur du selber entscheiden, wie (und ob) deine Anforderunge erfüllt werden. Da du nur einen Kanal zur Verfügung hast, sind Verzögerungen von Thread B gegenüber von Thread A prinzipbedingt nicht ganz zu vermeiden.
Das Leben ist wie ein Tennisball.
dschoni
User
Beiträge: 2
Registriert: Dienstag 11. September 2012, 15:34

Hm danke schonmal soweit.
Die Kommunikation über die Schnittstelle läuft soweit ganz gut, allerdings hängts bei mir eher am Verständnis der Threads.
Bisher verstehe ich das so: Timer und User reihen ihre Anfragen in die Queue ein, der dritte Thread arbeitet die ab (Kommuniziert also mit der seriellen Schnittstelle).
Aber beim ausprogrammieren haperts. Sind die Queues globale Objekte oder Unterobjekte der Klasse (oder spielt das keine Rolle?)?
Beim ausführen erscheinen Fehlermeldungen mit Threadnummern. Diese sind teilweise deutlich größer als 4, heißt das ein Thread kann auch mit sich selbst parallel laufen?
Ich denke ich bräuchte mal Grundsätzliche Informationen: Jeder Thread ist eine Klasse? Eine Instanz der Klasse ist ein Thread?
Danke schonmal.
BlackJack

@dschoni: Globale Objekte sollte man vermeiden. Das ist unsauber und bereitet in der Regel mehr Probleme als dass es die Sache vereinfacht. „Unterobjekte der Klasse” macht als Aussage nicht viel Sinn — ich nehme mal an Du meintest hier Attribute eines Objekts vom Typ einer bestimmten Klasse. Man sollte Objekte den Objekten bekannt machen, die sie verwenden müssen.

Solange es nur maximal drei Thread-Nummern in einem Programmablauf gibt, ist es egal welche konkreten Werte die haben. Wenn Du in einem Programmablauf allerdings mehr als drei Thread-Nummern siehst, dann gibt es auch mehr als drei Threads, und das wäre dann etwas komisch. Wobei Du in Deinem Szenario auch nur *zwei* Threads explizit starten musst, denn den normalen Programmablaufstrang hast Du ja auch noch. Man startet ja sozusagen immer mit einem Thread.

Ein Thread kann nicht parallel zu sich selbst laufen. Es kann aber der gleiche Code in mehreren Threads parallel abgearbeitet werden.

Threads und Klassen sind orthogonal. Man kann den (start)code für einen Thread in eine Klasse verpacken, das muss man aber nicht. Und man kann dann den gleichen Code in mehreren Threads ausführen. Das Code oder Daten nicht fest zu einem Thread gehören, macht diese Art der nebenläufigen Programmierung teilweise ja so kompliziert und fehleranfälliger.

Ein Thread ist kein „Ding” also kleine Instanz einer Klasse sondern eher ein Konzept. Wenn man nur einen hat, dann kann man immer sagen das Programm führt jetzt nacheinander diesen und jenen Code aus. Bei mehreren wird gleichzeitig mehr als ein Codeabschnitt parallel [1]_ ausgeführt. Und man muss darauf achten dass die sich nicht bei der Datenmanipulation in die Quere kommen. In dem man sie nicht auf den selben Daten operieren lässt und da wo sich das nicht vermeiden lässt durch spezielle Sperrmechanismen (`threading.Lock` & Co.) dafür sorgt das zu einem Zeitpunkt immer nur ein Thread kritischen Code ausführen darf und die anderen Warten müssen, wenn sie zufällig den gleichen Code ausführen wollen. Der Datentyp `Queue.Queue` hat so einen Sperrmechanismus praktischerweise schon eingebaut.

.. [1] Bei der CPython-Implementierung setzt das „global interpreter lock” (GIL) dieser Paralellität gewisse Grenzen. Das führt aber nicht dazu dass man nicht grundsätzlich die gleichen Probleme und Gefahrenquellen hat, als man ohne GIL hätte. Das GIL schützt nur den Python-Interpreter selbst davor, nicht den Benutzercode der darauf läuft.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Mal zum Nachdenken:

Code: Alles auswählen

import itertools
import Queue
import threading
import random
import time


class Base(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
        self.queue = Queue.Queue()
    
    def put(self, data, source):
        self.queue.put((data, source))


class Source(Base):
    def __init__(self, target, name, first):
        Base.__init__(self)
        
        self.target = target
        self.name = name
        self.first = first
        
    
    def run(self):
        for i in itertools.count(self.first, step=2):
            self.target.put((i, self.name), self)
            
            (result, _) = self.queue.get()
            print "source %s: %d*%d = %d" % (self.name, i, i, result)
            
            time.sleep(random.random())


class Target(Base):
    def run(self):
        while True:
            (index, name), source = self.queue.get()
            
            print "target: task %d from %s" % (index, name)
            
            source.put(index**2, self)

t = Target()
a = Source(t, "even", 0)
b = Source(t, "odd", 1)

t.start()
a.start()
b.start()

t.join()
Das Leben ist wie ein Tennisball.
Antworten