thread/Variablen/Datenaustausch/IDs

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
NWA
User
Beiträge: 36
Registriert: Mittwoch 3. Februar 2021, 11:40

Hallo, ich möchte gerne Daten zwischen einem Thread und der umgebende Klasse tauschen.

Einfaches Beispiel aus Ernesti, Python 3, S.590, wo 'self.zahl' initialisiert und in 'run()' auch benutzt wird:

Code: Alles auswählen

import threading
class PrimzahlThread(threading.Thread):
	def __init__(self, zahl):
		super().__init__()
		self.Zahl = zahl
	def run(self):
		i = 2
		while i*i <= self.Zahl:
		if self.Zahl % i == 0:
			print(f"{self.Zahl} ist nicht prim, "
				f"da {self.Zahl} = {i} * {self.Zahl // i}")
			return
		i += 1
	print(f"{self.Zahl} ist prim")

Code: Alles auswählen

meine_threads = []
eingabe = input("> ")
while eingabe != "e":
	try:
		thread = PrimzahlThread(int(eingabe))
		meine_threads.append(thread)
		thread.start()
	except ValueError:
		print("Falsche Eingabe!")
	eingabe = input("> ")
for t in meine_threads:
	t.join()
In meinem folgenden Beispiel geht das aber nicht! Wenn ich es starte, dann wird mir für 'self.ressource' jedesmal ein anderer Wert ausgegeben. Ich gebe die IDs aus und stelle fest, dass jedesmal andere ausgegeben werden! Sogar in der 'Init' und innerhalb von 'run()'!
Ausgabe vorweg:

Code: Alles auswählen

init(): 1668288720816 ( aus init )
get(): 1668288720816 ( aus init )
main #1: aus init

run(): 2795818561968 ( aus init )
run(): 2795818544112 ( aus run )
run(): 2795818544112 ( aus run )
run(): 2795818544112 ( aus run )
get(): 1668288720816 ( aus init )
main #2: aus init

Code: Alles auswählen

import multiprocessing as mp
import time

class Threadclass(mp.Process):
    def __init__(self):
        super().__init__()
        self.daemon = True  # wenn True, dann wird thread mit main beendet
        self.ressource = "aus init"  # Ressource die aus beiden Klassen gefunden werden soll
        print("init():", id(self.ressource), "(", self.ressource, ")")

    def run(self):
        while True:
            time.sleep(2)
            print("run():", id(self.ressource), "(", self.ressource, ")")
            self.ressource = "aus run"
            print("run():", id(self.ressource), "(", self.ressource, ")")

    def get(self):
        print("get():", id(self.ressource), "(", self.ressource, ")")
        return self.ressource

Code: Alles auswählen

from threadfile import Threadclass
import time

if __name__ == "__main__":
    thread1 = Threadclass()  # thread erstellen
    thread1.start()  # thread starten

    print("main #1:", thread1.get())
    time.sleep(5)
    print("main #2:", thread1.get())
Seit Stunden probiere ich rum und bekomme langsam Kopfschmerzen...
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei Deinem ersten Code sind die Einrückungen kaputt. Da muß man dann raten was gemeint ist. Das liegt daran, dass Du Tabs mit Leerzeichen mischst. Man rückt aber immer mit 4 Leerzeichen pro Ebene ein und benutzt nie Tabs.

Ein Attribut ist eigentlich (fast) nie sinnvoll in einer Schleife zu benutzen. Attribute sollen ja Zustand über das Ende der Methode hinaus speichern, solange sie also innerhalb der Methode geändert werden, sind das keine Attribute.
Die Klasse besteht aus einer Methode und einem Attribut, das ist also eine überflüssige Klasse und kann viel einfacher als Funktion geschrieben werden.
Präfixe wie my_ oder meine_ sind unsinnig, da sie nichts aussagen.
Wenn man etwas vor der while-Schleife wiederholt, was innerhalb der Schleife schon vorkommt, dann hat man eigentlich den Fall einer while-True-Schleife.
Das Beispiel wäre also richtigerweise:

Code: Alles auswählen

import threading

def teste_primzahl(zahl):
    i = 2
    while i*i <= zahl:
        if zahl % i == 0:
            print(f"{zahl} ist nicht prim, "
                f"da {zahl} = {i} * {zahl // i}")
            return
        i += 1
    print(f"{zahl} ist prim")

threads = []
while True:
    eingabe = input("> ")
    if eingabe == "e":
        break
    try:
        zahl = int(eingabe)
    except ValueError:
        print("Falsche Eingabe!")
    else:
        thread = threading.Thread(target=teste_primzahl, args=(zahl,))
        threads.append(thread)
        thread.start()
for thread in threads:
    thread.join()
Bei Deinem zweiten Beispiel hast Du ja keinen Thread, sondern ein Multiprocessing. Die Klassennamen sind also falsch.
Dass das in zwei Module aufgeteilt ist, ist unsinnig bei solchen kurzen Modulen. `threadfile` ist auch kein guter Modulname, denn das hat ja mit Files gar nichts zu tun.
Dann darf man nicht sowohl aus dem Thread heraus, als auch vom Hauptprogramm einfach so auf Daten zugreifen.
Und in Deinem Fall von Multiprocessing, wo Du statt Threads Prozesse hast, geht das, wie Du gerade selbst bewiesen hast, gar nicht.

Man benutzt passende Datenstrukturen, wie Queues, Events, etc.
NWA
User
Beiträge: 36
Registriert: Mittwoch 3. Februar 2021, 11:40

Vielen Dank Sirius3 für deine Antwort.
Die Einrückungen bei meinem ersten Beispiel sind nicht kaputt, sondern ich hatte sie aus dem Ernesti-Buch kopiert und dann hier manuell eingerückt. Das mit den Tabs gewöhne ich mir noch ab...
Alle deine Korrekturen sind bestimmt korrekt; wie schon gesagt: Das erste Beispiel ist aus dem Ernesti-Buch.
Du schreibst aber, dass du keine Klasse, sondern Funktionen benutzen würdest. Ich muss aber später mehrere Objekte der Klasse anlegen, da würde ich nicht wissen wie ich das ausschliesslich mit Funktionen umsetzen sollte.

Deine Hinweise zum zweiten Beispiel muss ich nochmal richtig durcharbeiten.
NWA
User
Beiträge: 36
Registriert: Mittwoch 3. Februar 2021, 11:40

Also, ich habe mich mit dem Thema eingehend beschäftigt.
Dein Beispiel basiert auf join- also das sich der thread beendet und somit immer wieder neu gestartet wird. Das habe ich nicht gemacht, weil ich las das man threads mit Semaphoren/while True unterbricht. Ich hatte auch schon überlegt threads immer wieder neu zu starten, dachte aber das es viel länger dauert den thread zu starten und zu beenden, als ihn einfach mit while/True fortzusetzen.
Des Weiteren ist mir ein Fehler unterlaufen: Ich habe den falschen Code aus dem Ernesti-Buch kopiert.
Dort ist ein Beispiel mit multiprocessing, wo sogar auch aus run() auf ein Attribut zugegriffen wird (Ernesti, Python 3, S. 600):

Code: Alles auswählen

import multiprocessing

class PrimzahlProzess(multiprocessing.Process):
    def __init__(self, zahl, einauslock):
        super().__init__()
        self.Zahl = zahl
        self.EinAusLock = einauslock
    def run(self):
        i = 2
        while i*i <= self.Zahl:
            if self.Zahl % i == 0:
                with self.EinAusLock:
                    print(f"{self.Zahl} ist nicht prim, "f"da {self.Zahl} = {i} * {self.Zahl // i}")
                return
            i += 1
        with self.EinAusLock:
            print(f"{self.Zahl} ist prim")
Du hast zwar geschrieben das es nicht gehen würde aus dem Hauptprozess einfach so auf Daten zuzugreifen, aber im obigen Code greift run() einfach so auf self.zahl zu. Das ist auch meine Hauptfrage: Wieso greift bei mir run() auf ein anderes Attribut zu als das Hauptprogramm? Was passiert da im Hintergrund?
Benutzeravatar
__blackjack__
User
Beiträge: 13112
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NWA: Da wird nicht ”sogar” in `run()` auf ein Attribut zugegriffen. Das `Process`-Objekt existiert ja in dem Prozess in dem `run()` läuft, also kann da natürlich auch auf Attribute von diesem Objekt zugegriffen werden.

So ein `Process`-Objekt wird im Hauptprozess erzeugt, und für das `start()` wird ein neuer Prozess erstellt und das Objekt serialisiert, zu dem neuen Prozess geschickt, dort deserialisiert, und dann `run()` aufgerufen. Du hast dann in jedem der beiden Prozesse eine tiefe Kopie von dem Objekt, die nichts mehr miteinander zu tun haben. (Letzteres stimmt nicht so ganz, weil Objekte wie das `EinAusLock` dafür sorgen, dass sie sich in allen Prozessen auf die gleiche Ressource beziehen in dem sie in das (de)serialisieren eingreifen.)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Dieser `EinAusLock` funktioniert auch nur, wenn wirklich ALLE print-Ausgaben damit geschützt sind.
Solange Du nur eine Methode `run` hast, macht eine Klasse nie Sinn, da kann man noch so ein Konstrukt konstruieren.
Und zahl ist ja der Inputparameter für den Prozess, die kennt also Multiprocessing und kann den entsprechend dem anderen Prozess übergeben.
Der Rückweg funktioniert nicht.
NWA
User
Beiträge: 36
Registriert: Mittwoch 3. Februar 2021, 11:40

Ok, ich verstehe: An run() wird also "nur" eine tiefe Kopie übergeben. Die Namen scheinen gleich zu sein, aber die Inhalte und IDs sprechen doch dagegen...
Ich würde aber gerne aber mit Referenzen arbeiten. Besteht überhaupt diese Möglichkeit z.B. mit IDs etc. oder kann dieser Datenaustausch tatsächlich nur über Queue oder Events laufen?
Ich frage deshalb so ungläubig, weil in meinem Verständnis einer objektorientierten Sprache dachte, dass mehr in Objekten gearbeitet wird und nicht mit Kopien.
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man kann mit zwei Prozessen deren Speicher nunmal getrennt ist nicht mit Referenzen arbeiten. Sondern immer nur mit Kopien. Wie sonst soll das gehen?
Benutzeravatar
__blackjack__
User
Beiträge: 13112
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NWA: Das hat nichts mit der Sprache zu tun, sondern dass Du da *Prozesse* laufen hast. Die haben per Definition eigene Adressräume, das heisst ein Prozess kann nicht auf das zugreifen was beim anderen Prozess im Speicher gehalten wird. Datenaustausch geht über Queues und Pipes aus dem `multiprocessing`-Modul. Dabei werden Kopien zwischen den beiden Prozessen übertragen. Und man kann `ctypes`-Datenstrukuren per „shared memory“ zwischen Prozessen teilen oder noch ein paar wenige andere, spezielle Typen über einen Serverprozess. Auch hier muss man aber darauf achten, dass man das wo es nötig ist, entsprechend mit Sperren absichert.

Bei nebenläufiger Programmierung, egal wie die gemacht wird (Coroutinen, Threads, Prozesse, …) will man gemeinsamen Zustand so weit wie möglich vermeiden, weil da Probleme lauern. Insbesondere auch nichtdeterministische, also Fehler die mal auftreten und mal nicht, oder noch schlimmer, die ”nie” auffallen, bis sie dann eben doch mal zuschlagen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten