(Multi-)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
duodiscus
User
Beiträge: 97
Registriert: Sonntag 6. April 2014, 16:10

Hallo miteinander,
ich arbeite derzeit an einem Beispielprogramm, das einem Threads näher bringen soll. Und zwar möchte ich 3 Threads erstellen die meine Funktion FillList(a,b,c) parallel ausführen und eine Liste mit Zahlen füllen sollen und am Ende soll diese Liste ausgegeben werden. Mit der Funktion sleep möchte ich Bearbeitungszeiten simulieren.

Ich habe schon ein paar Beispiele zu Threading gesehen, aber bei mir gibt er leider nichts aus. Und ich weiß net so recht wo ich nun weitermachen muss bzw. was ich falsch verstanden habe.

Hier mein Beispiel:

Code: Alles auswählen

import threading, time

L = [ ]

def FillList(a, b, c):
    time.sleep(0.15)
    
    L = list(range(a,b,c))
    
    return L
    



thrF1 = threading.Thread(target = FillList, args = (100, 150, 2))
thrF2 = threading.Thread(target = FillList, args = (200, 250, 4))
thrF3 = threading.Thread(target = FillList, args = (300, 350, 5))

thrF1.start()
thrF2.start()
thrF3.start()

thrF1.join()
thrF2.join()
thrF3.join()

print(L)
print('Ende')
BlackJack

@duodiscus: Das `L` auf Modulebene hat nichts mit dem lokalen `L` in der Funktion zu tun. Die Funktion *erstellt* eine neue Liste und gibt die *zurück* an den Aufrufer. Nur dass das das `Thread`-Objekt welches die Funktion aufruft, überhaupt keinen Rückgabewert erwartet und der Wert damit auch nirgends gespeichert wird.

Du möchtest der Funktion eine Liste übergeben, in die dann Werte eingetragen werden. Und zwar allen Aufrufen/Threads *die selbe* Liste, und dann vielleicht auch die Elemente einzeln an diese Liste anhängen und zwischen jedem Element kurz schlafen, damit man den Effekt sieht, dass diese drei Threads nebenläufig und nicht sequenziell ablaufen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

das sind gleich zwei Fehler auf einmal. Der erste Fehler ist, dass du die Regeln von Namensräumen unter Python nicht nicht kennst. Die Liste "L" in "FillList" ist eine andere Liste als die aus Zeile 3. Die in der Funktion erzeugte Liste existiert nur lokal in der Funktion, danach nicht mehr. Dann stellt sich natürlich noch die Frage, warum du L überhaupt als Ergebnis zurücklieferst. Threads haben kein Ergebnis. Das sieht ein wenig danach aus, als versuchst du dich durch den Code zu raten. Die Lösung zu dem Problem ist ganz einfach: Übergebe, zusätzlich zu "a", "b" und "c" die Liste, welche gefüllt werden soll.

Das zweite Problem ist, dass du überhapt keine Werte an die Liste anhängst. Du erstellst bei jedem Aufruf eine komplett neue Liste und überschreibst diese einfach mit den Werten aus ``range(a, b, c)``. Wenn du etwas an eine Liste anhängen willst, dann musst du das auch tun. Der Interpreter kann nicht wissen, was du eigentlich vor hast.

Als nächstes fällt dann sofort auf, dass dein ganzer Code auf modulebene steht. Dass solltest du dir gleich abgewöhnen und entsprechend in Funktionen verpacken. In deinem Fall bietet sich eine einfach main-Funktion an, welche die Erzeugung, das Starten und das Zusammenführen der Threads enthält. Dafür gibt es in Python auch in Idiom:

Code: Alles auswählen

def main():
    thrF1 = threading. ...

    if __name__ == "__main__":
        main()
Damit ist sichergestellt, dass du deinen Code auch importieren kannst. Somit ist er besser wiederverwendbar.

Dann solltest du dir auch gleich die vernünftige Vergabe von Namen angewöhnen. "thrF1" hat gar keine Aussagekraft, lasse dir hier etwas besseres einfallen. Am besten einen aussagekräftigen Namen, der dir (und uns helfenden Lesern) beim Verständnis hillft. Sonst weißt du in zwei Wochen nicht mehr, was sich eigentlich hinter dem Namen verbirgt.

Eine weitere schlechte Idee ist das Durchnummerieren von Namen. Wenn du anfängst Nummern zu vergeben, dann verwendest du die falsche Datenstruktur und möchtest auf Tupel oder Listen, ggf. auch ein Dictionary, umsteigen. Bei der Gelegenheit könntest du auch einen Blick auf PEP 8 werfen, dort werden einige Konventionen aufgeführt. So sollten Funktionsnamen durch Unterstriche geteilt sein, also "fill_list", statt "FillList" und aus Kleinbuchstaben bestehen. "FillList" deutet im Pythonkontext, und in vielen anderen Sprachen auch, auf eine Klasse hin. Auch sind die ganzen Leerzeichen um die Gleichheitszeichen bei den Parametern störend.

Und natürlich solltest du auch gleich vergessen, dass es globale Variablen gibt. Wenn du dir nicht sicher bist, dass du eine globale Variable verwenden solltest oder nicht, dann ist es ein klares Zeichen dafür, dass du sie nicht verwenden solltest. Wie ich oben schon geschrieben habe, ist das für "L" auch vollkommen unnötig, wenn du die Liste als Parameter übergist.

Auch die Umsetzung deines Ansatzes hat Probleme. Du wartest 0,15 Sekunden zu Beginn der Füllfunktion und füllst diese dann auf einen Schlag. Damit wirst du keinen Effekt feststellen. Du musst schon über die Elemente des range-Objekts iterieren und diese schrittweise in die Liste einfügen und zwischen den einzelnen Schritten warten.

Dann kommt noch hinzu, dass du den Zugriff auf die Liste "L" schützen musst, damit du nicht versehentlich zur selben Zeit in die Liste schreibst. Das dürfte unter CPython in diesem Fall eigentlich nicht passieren, aber bereits bei unwesentlich komplexeren Programmen (oder einem anderen Interpreter) wirst du auf das Problem stoßen. Do solltest dir dazu mal "threading.Lock" anschauen.

Das waren jetzt zwar recht viele Informationen, aber Threading ist eben ein recht komplexes Thema. Bereits bei so einfach Aufgaben.
Das Leben ist wie ein Tennisball.
BlackJack

Mal in ordentlich:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import print_function
from time import sleep
from threading import Thread
 
 
def fill_list(values, start, stop, step):
    for i in range(start, stop, step):
        values.append(i)
        sleep(0.05)
   

def main():     
    values = list()
    threads = [
        Thread(target=fill_list, args=(values, i * 100 + 100, i * 100 + 150, j))
        for i, j in enumerate([2, 4, 5])
    ]
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()
    print(values)
    print('Ende')


if __name__ == '__main__':
    main()
Was EyDu zur Komplexität und dem Schutz per `Lock` sagt, stimmt grundsätzlich allerdings darf man annehmen, das Operationen wie das anhängen eines Elements an eine Liste oder das Eintragen eines Wertes in ein Wörterbuch, atomare Operationen sind. Auch in anderen Python-Implementierungen.
duodiscus
User
Beiträge: 97
Registriert: Sonntag 6. April 2014, 16:10

Danke für die sehr ausführlichen Antworten. Ich habe es nun lösen können und es funktioniert nun auch einwandfrei.
Ich habe eine for-Schleife in die Funktion eingesetzt und die Werte an die Liste zurückgegeben.
#

Code: Alles auswählen

for i in range(a, b+1, c)
Habe mir die Tipps von 'EyDu' mal zu Herzen genommen und schaue mal genauer in die Strukturierung demnächst 8)
Antworten