Threading, Multiprocessing richtig anwenden

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.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Im Kern musst du folgendes machen, um den Thread zu einer Ausgabe zu bewegen:

- Von `threading.Thread` ableiten und in `.run()` eine Schleife bauen, welche deine CSV-Daten ausliest.

- Innerhalb dieser Schleife die einzelnen Datensätze in die Queue ablegen.

- Benachrichtigen, wenn die Aktualisierung beendet wurde, damit man dann von außen auf die Daten zugreifen kann.

In Quelltext ausgedrückt, könnte das in etwa so aussehen (ungetestet):

Code: Alles auswählen

class Updater(threading.Thread):
    def __init__(self, queue, csv_filename):
        self.queue = queue
        self.csv_filename = csv_filename
        self._finished = threading.Event()

    def run(self):
        with open(csv_filename) as csv_file:
            for row in csv.reader(csv_file):
                # Nur als Beispiel gedacht
                self.queue.put({
                    'name': row[0], 'menge': row[1], 'preis': row[2]
                })
        self._finished.set()

    def has_finished(self):
        return self._finished.is_set()


class MyTkWindow(object):
    # ...

    def update_data(self):
        if not self.updater:
            # Erster Durchlauf -> Thread muss gestartet werden.
            self.updater = Updater(self.queue, self.csv_filename)
            self.updater.start()
        if not self.updater.has_finished():
            # Aktualisierung läuft noch -> versuche es in 500ms erneut.
            self.root.after(500, self.update_data)
        else:
            while not self.queue.empty():
                # Elemente der Queue nacheinander an GUI übermitteln.
                self.set_gui_data(self.queue.get())
            # Thread-Objekt "wegschmeißen", da es nicht wiederverwendet werden kann.
            # Dient auch als Hinweis, dass ein neuer Thread gestartet muss, wenn die
            # Aktualisierung mittels `.update_data()` später erneut angestoßen wird.
            self.updater = None
Wichtig ist halt, zu verstehen, dass der aufrufende Thread weiterhin Zugriff auf die Queue benötigt. Ich gehe hier davon aus, dass die Queue vorab erstellt und an`self.queue` gebunden wurde, z.B. in `__init__()`.

So wie ich dich verstanden habe, willst du die Überführung der aktualisierten Daten in die GUI in einem Rutsch erledigen. Daher wird die `update_data()`-Methode in meinem Beispiel immer wieder aufgerufen bis der Thread mitgeteilt hat, dass nun alle Daten aus der CSV-Datei in den Speicher gelesen wurden. In dem 500ms dazwischen, kann die GUI etwas anderes tun. Eine `while`-Schleife würde die GUI an dieser Stelle einfrieren und den Effekt, den man durch das Threading erhalten möchte, letztlich nutzlos machen.

Und am Ende wird dann wird der Zwischenspeicher ausgelesen und die einzelnen Änderungen an die GUI zum Einpflegen übergeben.
Zuletzt geändert von snafu am Dienstag 10. November 2015, 16:59, insgesamt 1-mal geändert.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Muss es wirklich sein dass bei Threads jedes mal das threading Modul und Queues ausgekramt werden auf dessen Basis dann in jedem Forenthread zum Thema das Rad neuerfunden wird? Es gibt schon seit *Jahren* concurrent.futures in der Standard Library mit dem wesentlich einfacher zu benutzenden ThreadPoolExecutor. Es gibt auch einen backport für Python 2, wenn man Python 3 nicht benutzen will oder kann.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu, Danke für Deinen Code! :wink:
Bin gerade noch auf einer anderen Baustelle ..., daher konnte ich mich im Moment noch nicht weiter dem Thema widmen.
Dies werde ich bald nachholen, denn für das Umsetzen Deines Inputś, brauche ich Zeit damit nichts schief läuft.

Hallo DasIch, habe bei meiner Recherche auch von concurrent.futures gelesen, soll aber nicht ganz so leistungsfähig sein.
Vielleicht war ich auch nur auf der falschen Seite ..., konnte aber nicht so viel über concurrent.futures erfahren.

PS: Ich verwende Python3!
BlackJack

@Nobuddy: Was ist denn an der Stelle mit Leistungsfähigkeit gemeint? Und selbst wenn: Das etwas fehlerfrei funktioniert ist doch auch etwas Wert. Das ist getestet und wird von vielen benutzt, im Gegensatz zu dem x-ten selbst gebastelten Pool-Manager.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo BlackJack,

das mit concurrent.futures, werde ich mir nochmals genauer anschauen.
Leider konnte ich dazu nicht all zu viele Beiträge dazu finden, daher habe ich Threading, Multiprocessing favorisiert, das was laut Beiträgen mehr angewendet wird.

Wo und welche Anleitungen, Beiträge sind empfehlenswert?

Grüße Nobuddy
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wieso wird hier von einem Pool-Manager geschrieben? Das Vorhaben erfordert doch überhaupt keinen Thread-Pool, sondern nur einen einzelnen Thread, der etwas im Hintergrund erledigt und Bescheid gibt, wenn er fertig ist.

Gut, man könnte für jede CSV-Datei einen einzelnen Thread starten, der sich entsprechend nur um den Inhalt "seiner" Datei kümmert und das Dictionary mit dem Ergebnis für die jeweilige Datei in einem Rutsch ausliefert. Dann wäre `concurrent.futures` tatsächlich sinnvoller.
BlackJack

@snafu: Warum keinen Pool? Die API ist auch für eine Aufgabe schön einfach. Und man könnte eventuell die Aufgabe selbst tatsächlich parallelisieren. Und wenn das keine geteilten Daten erfordert auch Prozesse statt Threads verwenden. Mit der gleichen API.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Angepasst für das `concurrent.futures`-Modul könnte das verteilte Abarbeiten einer CSV-Datei auf einer generischen Funktion wie dieser basieren:

Code: Alles auswählen

CSV_CONVERTERS = {'customers.csv': convert_customers, 'suppliers.csv': convert_suppliers, ...}

def convert(csv_filename, converters=CSV_CONVERTERS):
    basename = os.path.basename(csv_filename).lower()
    converter = converters.get(basename)
    if not converter:
        raise ValueError("don't know how to convert {!r}".format(basename))
    with open(csv_filename) as csv_file:
        return converter(csv.reader(csv_file))
Die spezifischen Konvertierer würden dann dementsprechend ein `csv.reader`-Objekt erwarten und dessen Datensätze in ein Dictionary übertragen und dieses Dictionary am Ende zurückliefern. Ein `ThreadPoolExecutor`-Objekt könnte zum Beispiel mit seiner `map`-Methode dann die `convert`-Funktion mit den verschiedenen CSV-Dateinamen aufrufen.

Hier bleibt aber weiterhin das Problem bestehen, dass die GUI weiterlaufen soll, während die Umwandlungen stattfinden. Und eben deshalb hatte ich in meinem vorherigen Beispiel das `threading`-Modul verwendet in Verbindung mit einer entsprechenden Benachrichtigung am Ende die Abarbeitung, weil mir dies in diesem Punkt geeigneter erschien.
BlackJack

@snafu: Wieso bleibt das Problem mit der GUI bei `concurrent.futures`? Das führt die Funktionen doch nebenläufig aus. Und wenn man nebenläufig auf das Ende der nebenläufigen Funktionen warten will, dann kann man das doch auch über eine Funktion machen die das Modul benutzt. Zum Beispiel in dem man dem `Future` von der Funktion eine Rückruffunktion gibt.
Daikoku
User
Beiträge: 66
Registriert: Montag 20. April 2015, 21:14

@Nobuddy, beim Einstieg in das Thema Threading, Multiprocessing hat mir das Tutorial von Doug Hellmann geholfen.

Startseite : https://pymotw.com/2/ - last updated Jul 12, 2015
zum Thema Threading, Multiprocessing : https://pymotw.com/2/optional_os.html
zum Thmea Queue : https://pymotw.com/2/Queue/index.html

Um Dir weitere Informationen geben zu können, wäre es sinnvoll, zu Wissen mit welchem Betriebssystem Du arbeitest.

Windows und Threading ist ein völlig anderes Thema als Linux und Threading oder OS X und Threading.
BlackJack

@Daikoku: Wieso ist Threading je nach System ein anderes Thema? Die `threading` oder `concurrent.futures`-API ist bei allen Systemen gleich, und wenn man etwas nebenläufig zur GUI ausführen möchte ohne das selber durch regelmässige Aufrufe ”antreiben” zu müssen, bleibt einem ausser Threading kaum eine andere Wahl.
Daikoku
User
Beiträge: 66
Registriert: Montag 20. April 2015, 21:14

@BlackJack:
Ich habe lange darüber nachgedacht, ob ich das hier schreibe oder nicht.
Der einfachste Weg wäre, ich Denke mir meinen Teil und behalte das Ergebnis für mich. Auf der anderen Seite, bietet die Kontroverse auch Chancen.

Ich verstehe Deinen Einwand.
Aus der Sicht eines Softwareentwicklers ist der Multithreading Support unter Windows- und POSIX-Threads zu erst einmal völlig gleich.
Differenzen ergeben sich nur aus den unterschiedlichen Namen der Funktionen der jeweiligen API zum Betriebssystem.
Dieses wird unter Python jedoch nicht sichtbar, weil Python selber, eine bzw. mehrere APIs bereitstellt, die unter allen Betriebssystemen gleich sind.
Dennoch gibt es riesige Unterschiede wie Threads vom Betriebssystem verwaltet, geplant und ausgeführt werden.
Auch spielt die Prozessor-Mikroarchitektur, auf welcher das jeweilige Betriebssystem zur Ausführung gelangt, eine gewisse, teilweise aber auch entscheidende Rolle.

Python Threads sind immer real system threads der jeweiligen Betriebssysteme, also POSIX Threads für Mac OS X und Unix/Linux sowie Windows Threads für Windows Betriebssysteme.

Mit Python Threads kann man keine Nebenläufigkeit Programmieren.

Die Nebenläufigkeit, auch Parallelität (englisch concurrency) genannt, ist in der Informatik die Eigenschaft eines Systems, mehrere Berechnungen, Anweisungen oder Befehle gleichzeitig ausführen zu können. - Quelle Wikipedia.

Threading und Multiprocessing richtig anwenden, ist eines der zentralen Themen in der Softwareentwicklung.

Aus diesem Grund finde ich es sehr Schade, das hier bei diesem Thema kaum etwas sinnvolles bei raus gekommen ist.
Ich glaube auch nicht, das der Fragesteller seinem ursprünglichen Anliegen, sich diesem Thema Schritt für Schritt zu näheren, wirklich näher gekommen sein wird.

Er wanderte im finsterem Tal, und an Stelle ihm einen Weg hinaus zu zeigen, oder ihm wenigstens ein Navigationssystem an die Hand zu geben, zieht die Karawane weiter und lässt den Fragesteller weiter wandern, in noch tieferer Dunkelheit zurück.

Weiter Links: http://www.dabeaz.com/python/UnderstandingGIL.pdf

Multicore Application Programming: For Windows, Linux, and Oracle Solaris, 9. November 2010 von Darryl Gove
Leseprobe : https://books.google.de/books?id=NF-C2ZQZXekC&pg

Ich denke ein so wichtiges Thema hat es nicht verdient, hier so behandelt zu werden.
Wir Alle sollten uns einmal Gedanken machen, wie man dieses Thema so aufbereiten könnte, das es Einsteigern eine Chance zum Verständnis,
aber auch allen Anderen einen echten Mehrwert bietet.

@Alle: Ich möchte nicht mit dem Finger auf andere zeigen und schon gar nicht dann, wenn dabei drei Finger auf mich selber zurück zeigen.
Auch möchte Ich dies hier nicht als Kritik an BlackJack, oder an irgend jemanden sonst verstanden wissen.
Alle Personen die sich in diesem Forum engagieren verdienen Respekt, Anerkennung und Dank für das, was sie hier jeden Tag leisten.
BlackJack

@Daikoku: Ich weiss jetzt aber immer noch nicht welchen Unterschied das nun *praktisch* machen soll. Man kann mit Threads in Python durchaus nebenläufig programmieren, zum Beispiel im vorliegenden Fall wo eben etwas neben der GUI-Hauptschleife laufen soll. Wo ist da beim Vorgehen der Unterschied bei den genannten Betriebssystemen? Was muss ich da unter Windows anders machen als unter Linux oder MacOS? Ich habe da noch nichts von riesigen Unterschieden gemerkt.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Daikoku hat geschrieben:Dennoch gibt es riesige Unterschiede wie Threads vom Betriebssystem verwaltet, geplant und ausgeführt werden.
Windows und Linux benutzen unterschiedliche Scheduler. Der Windows Scheduler ist etwas komplizierter und scheint primär zu versuchen Rechenzeit möglichst fair auf Anwendungen zu verteilen statt auf Threads. In der Praxis dürfte aber dies keinen Unterschied machen, von ein paar Ausnahmen abgesehen.
Auch spielt die Prozessor-Mikroarchitektur, auf welcher das jeweilige Betriebssystem zur Ausführung gelangt, eine gewisse, teilweise aber auch entscheidende Rolle.
Glücklicherweise nutzen sowieso alle x86 bis auf den Embedded Bereich der uns aber egal sein kann, womit uns auch die Mikroarchitektur egal sein kann.
Mit Python Threads kann man keine Nebenläufigkeit Programmieren.
Dieser Satz...
Die Nebenläufigkeit, auch Parallelität (englisch concurrency) genannt, ist in der Informatik die Eigenschaft eines Systems, mehrere Berechnungen, Anweisungen oder Befehle gleichzeitig ausführen zu können. - Quelle Wikipedia.
widerspricht sich mit diesem Satz aus dem Artikel als auch dem restlichen Artikel. Der Artikel ist allerdings auch ohnehin problematisch, so ein Wort wie "gleichzeitig" sollte man in diesem Kontext besser vermeiden.
Daikoku
User
Beiträge: 66
Registriert: Montag 20. April 2015, 21:14

Hallo BlackJack,

o.k. ich werde mich bemühen, es verständlicher zu machen.

Können wir uns zunächst einmal darauf verständigen, das "Understanding the Python GIL" von David Beazley als Referenz herangezogen werden kann ?
Oder gibt es hierzu irgendwelche Einwände ?

Du musst nichts anderes machen. Das kannst Du ja auch gar nicht, da die jeweilige Python API, Dir die Möglichkeiten vorgibt.
Wenn Du Deinen Code erstellt hast, läuft dieser sowohl auf dem einen, wie auf dem anderem Betriebssystem.
Aber in dem Augenblick, wo dieser ausgeführt wird, hast Du keinerlei Kontrolle mehr darüber, wie das Betriebssystem damit umgeht und genau da
gibt es Unterschiede im handling, was dann auch zu unterschiedlichen Ergebnissen führen kann. Muss nicht, kann aber.
Ich werde mir Gedanken machen, wie ich Dir das praktisch verständlich machen kann. Werde dies nachreichen.
Ich denke, wir werden hier auf einen Nenner kommen.

Es kann immer nur ein Thread zur gleichen Zeit abgearbeitet werden. Die GIL lässt nichts anderes zu.
Das ist im Understanding the Python GIL auch genauso erklärt. Könntest Du Dir das, bitte einmal anschauen.
Vielleicht habe ich ein understanding Problem ? Hier liegen wir im understanding sehr weit auseinander.
Daikoku
User
Beiträge: 66
Registriert: Montag 20. April 2015, 21:14

@DasIch

Könntest Du bitte einen Vorschlag machen, wie wir Nebenläufigkeit definieren sollten, damit wir alle auch das Selbe darunter verstehen.

Mit Bezug auf die Mikroarchitektur werde ich zu einem späteren Zeitpunkt noch einmal zurück kommen. Ich bitte um etwas Geduld,
da ich im Intel Developer Network etwas nachschauen muss.
Daikoku
User
Beiträge: 66
Registriert: Montag 20. April 2015, 21:14

@DasIch, das nachstehende nur so zur Information, weil Du Dich auch mit PyPy beschäftigst.
Dies hat keinen Bezug zu diesem Thema oder einem anderem Thema. Es dient nur zum Informationsaustausch.

Introducing Pyston: an upcoming, JIT-based Python implementation https://blogs.dropbox.com/tech/2014/04/ ... ementation

Pyston 0.4 released : http://blog.pyston.org/ - https://github.com/dropbox/pyston
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Daikoku hat geschrieben:Oder gibt es hierzu irgendwelche Einwände ?
Ich hätte da einen Einwand zu: Der GIL ist zwar ein Pferdefuss von CPython, hat aber keinen Einfluss darauf, wie das OS Threads behandelt. Letzteres ist Sache der Scheduler. Der GIL führt lediglich dazu, das nur ein Thread eines CPythoninterpreters zu einer bestimmten Zeit laufen kann. Es hängt vom Scheduler ab, ob und was er an Threads/Prozessen prüft und Rechenzeit einräumt. (Linux kann z.B. auf blockierte Ressourcen wie file descriptors, Mutexe etc. prüfen und ggf. überspringen)
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Daikoku hat geschrieben:@DasIch

Könntest Du bitte einen Vorschlag machen, wie wir Nebenläufigkeit definieren sollten, damit wir alle auch das Selbe darunter verstehen
Man sollte einfach die Englischen Begriffe Concurrency und Parallelism benutzen. Diese Begriffe sind etabliert und haben eine eindeutige Definition.

Der GIL hat übrigens durchaus Einfluß über das Einschränken von Parallelism hinaus. Konkret führt dieser nämlich dazu dass wenn man mehrere Threads benutzt, Threads die viel CPU Zeit benötigen im Vergleich zu Threads die eher mehr IO machen, bevorzugt werden. Das hat massive Auswirkungen auf das Design von Anwendungen die viel Netzwerkkram machen. David Beazley hat in seinem Talk zu genau diesem Thema, eine sehr gute Demonstration zu diesem Problem. Der Rest des Talks ist übrigens auch sehenswert.

Edit: Wenn ich grad drüber nachdenke, könnte dass auch ein gutes Argument gegen Threads bei Anwendungen mit GUIs sein. Je nachdem inwieweit der GIL aufgehoben wird oder nicht und wieviel Arbeit in Python passiert, könnte CPU lastige Arbeit in Threads auslagern die GUI nahezu unbenutzbar machen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@DasIch: natürlich kann man immer ein Beispiel finden, dass irgendein System nicht mehr richtig funktioniert. In 99.9% der Fälle sind diese Beispiele aber von der Art, "würde ich nicht gegen das System sondern mit dem System programmieren, gäbe es auch eine einfache Lösung ohne das Problem". Gerade bei IO oder GUI spielt das GIL so gut wie gar keine Rolle, weil da die meisten Prozesse eh warten. Und sollte ein rechenintensiver Thread dabei sei, so wird der auch oft genug unterbrochen, dass ein Nutzer davon nichts spürbar mitbekommt.
Antworten