Frage zu Multithreading!



  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    
    namespace CodePlanet.Articles.Multithreading
    {
        class StandardThread
        {
            public static void Main(string[] args)
            {
                Thread t1 = new Thread(new ThreadStart(Incrementer)); // Das dies nicht funktioniert habe ich gesehen...
                Thread t2 = new Thread(new ThreadStart(Decrementer)); // Das dies nicht funktioniert habe ich gesehen...
                Thread t3 = new Thread(new ThreadStart(Multi)); // Das dies nicht funktioniert habe ich gesehen...
    
                for (int i = 1; i < 4; i++)
                {
                    // Threads starten
                    t1.Start(i);    // zB. Abspeichern der Werte in "C:Test1{0}.csv" , i
                    t2.Start(i);    // zB. Abspeichern der Werte in "C:Test2{0}.csv" , i
                    t3.Start(i);    // zB. Abspeichern der Werte in "C:Test3{0}.csv" , i
    
                    // #################################################################################################
                    /*     Ziel:    Es sollen mehrere berechnungen "zeitgleich" ausgeführt werden um zeit zu sparen 
                     *              (natürlich könnte ich das auch nacheinander abarbeiten lassen aber da ich datenmengen jenseits 
                     *              von gut und böse berechnen möchte, wollte ich auch meinen computer ganz ausschöpfen! Momentan 
                     *              läuft dieser nur mit 20-30% CPU-Auslastung)
                     * 
                     * 1. Frage:    Wann kommt das programm hier an: Wenn alle drei threads zuende gelaufen sind??
                     *              Bzw. Wie programmiere ich das, damit er erst hier weitermacht nachdem alle drei threads zuende 
                     *              sind!! (damit keine überlappungen von berechnungen passieren)
                     *              
                     * 2. Frage:    Ich habe einen quadcore PC, darf ich dann nur maximal 4 threads öffnen?
                     *              Bzw. Würden sich die threads bestenfall so gut aufteilen, das ein thread den ersten CPU-Kern 
                     *              benutzt, der zweite den zweiten CPU-Kern...
                     *              Oder habe ich da einen denkfehler?
                     *              
                     * P.s. Ich bin absoluter programmieranfänger, also bitte ich um nachsicht für meine fragen.
                     */
                }
            }
    
            public static void Incrementer(int Max)
            {
                for (int i = 0; i < Max; i++)
                {
                    for (int a = 0; a < Max; a++)
                    {
    
                        Console.WriteLine("{0} + {1} = {2}",i,a,(i+a));
                        Console.ReadKey();
                    }
                }
            }
    
            public static void Decrementer(int Max)
            {
                for (int i = Max; i >= 0; i--)
                {
                    for (int a = Max; a >= 0; a--)
                    {
                        Console.WriteLine("{0} - {1} = {2}", i, a, (i - a));
                        Console.ReadKey();
                    }
                }
            }
    
            public static void Multi(int Max)
            {
                for (int i = 0; i < Max; i++)
                {
                    for (int a = 0; a < Max; a++)
                    {
                        Console.WriteLine("{0} * {1} = {2}", i, a, (i * a));
                        Console.ReadKey();
                    }
                }
            }
        }
    }
    




  • Das heißt wenn ich nicht aktiv Multithreading einarbeite würde das Programm nur mit einem Prozessor (CPU-Kern) arbeiten?



  • werkla4 schrieb:

    Das heißt wenn ich nicht aktiv Multithreading einarbeite würde das Programm nur mit einem Prozessor (CPU-Kern) arbeiten?

    Ich bin verwirrt.
    Meinst du

    1. "wenn ich (mich) nicht aktiv (in das Thema) Multithreading einarbeite"
      oder
    2. "wenn ich nicht aktiv Multithreading (in mein Programm) einarbeite (einbaue)"
      ?
      Versuch bitte dich genauer auszudrücken 😉

    Eber egal. Die Antwort ist eigentlich in beiden Fällen die selbe.
    Wenn du nicht selbst Multithreading einbaust, dann wirst du nur begrenzt von Multithreading profitieren.
    D.h. wenn du z.B. bestimmte Libraries/OS-Funktionen verwendest, die intern schon mittels Multithreading optimiert sind, dann profitieren diese natürlich schon davon, ganz ohne dein Zutun.

    Dein eigener Code kann aber natürlich nur mehrere Cores verwenden, wenn du es selbst einbaust. Und dazu musst du natürlich erstmal wissen/verstehen wie man das macht.

    Was deinen Beispielcode angeht: der arbeitet einfach nicht korrekt. Du erzwugst drei Thread Objekte, startest sie, wartest nicht bis sie mir ihrer Arbeit fertig sind, und versuchst dann sie gleich nochmal zu starten (die for-Schleife).
    Was passiert wenn man Thread.Start() aufruft während der Thread noch läuft weiss ich ehrlich gesagt gar nicht, weil man das einfach nicht macht. Ich würde aber vermuten dass du einfach ne Exception bekommst.

    Wie du es korrekt machen kannst:

    public static void Main(string[] args)
            {
                for (int i = 1; i < 4; i++)
                {
                    using (Thread t1 = new Thread(() => Incrementer(i))) // using, damit alle Resourcen (z.B. Thread HANDLE) korrekt freigegeben werden
                    using (Thread t2 = new Thread(() => Decrementer(i))) // Die Thread-Funktion, inklusive aller Parameter, muss schon im Konstruktor übergeben werden...
                    using (Thread t3 = new Thread(() => Multi(i)))       // ...was man sehr schön mit Lambda-Funktionen machen kann, wie in diesem Beispiel
                    {
                        // Hier kommt das Programm an nachdem die drei Threads "vorbereitet" wurden
                        t1.Start(); // Thread starten
                        t2.Start();
                        t3.Start();
                        // Hier sind wir nachdem alle drei Threads gestartet wurden - die laufen dann aber vermutlich noch (es sei denn sie hatten SEHR wenig zu tun)
                        t1.Join(); // Warten bis Thread fertig ist
                        t2.Join();
                        t3.Join();
                        // Und hier kommen wir an, nachdem die Threads alle beendet sind
                    }
                }
            }
    

    Ungetestet. Ich hoffe ich hab' da keinen Fehler drin. Grundsätzlich macht man das auf jeden Fall so, also wenn was nicht passt sollte es schnell zu fixen sein (Tippfehler o.ä.).



  • Vielen dank für die antwort, es hat mir mehr verständnis in dieses thema gegeben. Da werde ich wohl join mit einbauen 😉

    Ist es auch möglich den threads(methode) parameter mitzugeben, so wie ich es versucht hatte?

    Noch eine frage: Wenn mehrere threads zeitgleich laufen und auf der festplatte etwas abspeichern wollen, da gibt es hoffentlich keine komplikationen (bei evtl. überlappung), das abspeichern wird bestimmt einen nach den anderen erledigt...?



  • Sorry, erste frage hat sich ja in dem fall erledigt!
    Aber wie ist das mit dem speichervorgang, zwecks festplatte? Werden diese speichervorgänge bestimmt nach und nach erledigt...!?



  • werkla4 schrieb:

    Ist es auch möglich den threads(methode) parameter mitzugeben, so wie ich es versucht hatte?

    Ja. Mit ParameterizedThreadStart. Der Parameter ist vom Typ object und muss gecastet werden.
    http://msdn.microsoft.com/en-us/library/system.threading.parameterizedthreadstart(v=vs.110).aspx
    Der Weg von hustbaer mit den Lambda-Ausdrücken ist aber der schönere.

    werkla4 schrieb:

    Noch eine frage: Wenn mehrere threads zeitgleich laufen und auf der festplatte etwas abspeichern wollen, da gibt es hoffentlich keine komplikationen (bei evtl. überlappung), das abspeichern wird bestimmt einen nach den anderen erledigt...?

    Kein Problem. Du schreibst ja in verschiedene Dateien.



  • werkla4 schrieb:

    // #################################################################################################
                    /*     Ziel:    Es sollen mehrere berechnungen "zeitgleich" ausgeführt werden um zeit zu sparen 
                     *              (natürlich könnte ich das auch nacheinander abarbeiten lassen aber da ich datenmengen jenseits 
                     *              von gut und böse berechnen möchte, wollte ich auch meinen computer ganz ausschöpfen! Momentan 
                     *              läuft dieser nur mit 20-30% CPU-Auslastung)
                     * 
                     * 1. Frage:    Wann kommt das programm hier an: Wenn alle drei threads zuende gelaufen sind??
                     *              Bzw. Wie programmiere ich das, damit er erst hier weitermacht nachdem alle drei threads zuende 
                     *              sind!! (damit keine überlappungen von berechnungen passieren)
                     *              
                     * 2. Frage:    Ich habe einen quadcore PC, darf ich dann nur maximal 4 threads öffnen?
                     *              Bzw. Würden sich die threads bestenfall so gut aufteilen, das ein thread den ersten CPU-Kern 
                     *              benutzt, der zweite den zweiten CPU-Kern...
                     *              Oder habe ich da einen denkfehler?
                     *              
                     * P.s. Ich bin absoluter programmieranfänger, also bitte ich um nachsicht für meine fragen.
                     */
    

    Ziel:
    Ok. Da Du aber Daten auf der HD speicherst, könnte das der Flaschenhals sein. Der Thread langweilt sich bei Zugriffen auf die Festplatte.

    1. Frage:
    Das Programm käme an sobald die Threads gestartet wurden. Die Lösung (join) wurde ja schon genannt.

    2. Frage:
    Nein beliebig viele. Auch auf Singlecore CPUs kann man Multithreading betreiben und ist für manche Anwendungen auch notwendig (z.B. ein Consolenbasierter Chat. Ein Thread wartet auf Benutzereingaben, ein anderer zeigt die Texte der Chatpartner). Um die Verteilung von Prozessen und Threads auf die einzelnen Kerne kümmert sich das OS. Man kann Einfluss nehmen indem man Thread/Prozess-Prioritäten setzt, aber das ist eher selten eine gute Idee.
    Jetzt aber nicht den Fehlschluss ableiten, dass ein Programm schneller läuft, je mehr Threads man erstellt. Hast Du X Kerne und erstellst Y>X Threads, kommt es zwangsläufig zur wechselseitigen Ausführung. Dazu laufen im Hintergrund viele weitere Dienste und das OS selbst, die Kernzeit beanspruchen.

    Zu Deinem P.S.
    Als absoluter Anfänger direkt an Multithreading? Das ist mutig 🙂

    Nachtrag: Lies Dir das hier durch http://www.albahari.com/threading/
    Du hast wie es aussieht eine bestimmte Aufgabe zu lösen die Threading-Techniken erfordern. In diesem Probekapitel findest Du alles notwendige dafür.



  • Ja, zu Frage (2) nochwas...
    Du kannst grundsätzlich so viele Threads erzeugen wie du magst. Sobald du genug Threads beinander hast um immer alle Cores auszulasten ändert sich an der Geschwindigkeit dabei kaum mehr was.

    Zwei Dinge die du aber evtl. bedenken solltest:

    1. Je mehr Threads du erzeugst, umso langsamer werden ggf. andere Programme. Das ist natürlich abhängig vom Scheduler des OS, aber wenn du "nett" sein willst solltest du nicht hunderte Threads erzeugen die alle permanent Vollgas geben. Zumindest nicht bei Desktop Anwendungen. Wenn deine Anwendung die einzig relevante auf dem ganzen System ist, ist es ziemlich egal.

    2. Wenn du aus mehreren Threads gleichzeitig grössere Datenmengen liest oder schreibst, dann bremst das eher als es hilft. Weil sich - vereinfacht gesagt - die Festplatte leichter tut wenn sie Files schön eines nach dem anderen schreiben kann, als wenn sie dauernd zwischen mehreren Files hin-und-her wechseln muss. (Das selbe trifft auch zu wenn man mit nur einem Thread abwechselnd kleine Stücke in unterschiedlichen Files liest oder schreibt, bzw. sogar wenn es das selbe File ist man aber dauernd in diesem File hin und herspringt.)
      Bei SSDs gilt prinzipiell das selbe, nur ist der Effekt dort wesentlich weniger ausgeprägt.



  • Ich danke euch für die gute auskunft. Ich denke soweit ist alles klar was dieses thema angeht, da muss ich doch glatt meine vorüberlegung zu meinem programm mit dem neuen wissen abgleichen 😉



  • werkla4 schrieb:

    Ich denke soweit ist alles klar was dieses thema angeht

    An dieser Stelle kann ich dir garantieren: Nein!



  • Sollte ich für jeden Thread eine Eigene Methode erstellen? Die Frage ist: "Wenn ein Thread schon in der Methode drin ist und ein zweiter Thread diese auch grad verwendet, Nicht das der 2.Thread mit falschen Parametern startet, nur weil schon Thread 1 ein paar Parameter geändert hat und noch in dieser Methode drin ist?
    Oder gibt es da keine Komplikationen, da jeder Thread seine "eigene" Methode nutzt?



  • Nein, es hat nicht jeder Thread seine eigene Methode.
    Aber jeder Thread hat eigene Parameter und lokale Variablen.

    D.h. du musst nicht für jeden Thread eine Kopie der Methode machen.



  • werkla4 schrieb:

    Sollte ich für jeden Thread eine Eigene Methode erstellen? Die Frage ist: "Wenn ein Thread schon in der Methode drin ist und ein zweiter Thread diese auch grad verwendet, Nicht das der 2.Thread mit falschen Parametern startet, nur weil schon Thread 1 ein paar Parameter geändert hat und noch in dieser Methode drin ist?
    Oder gibt es da keine Komplikationen, da jeder Thread seine "eigene" Methode nutzt?

    eine funktion mit lokalen variablen legt diese am stack ab, und da jeder thread seinen eigenen stack hat, hat auch jede funktion ihre eigenen kopien der variablen.
    anders ist das natürlich bei globalen variablen bzw. ganz allgemein variablen, die nicht funktions-lokal sind.

    int x=0;

    void foo()
    {
    int a=3,b=0; <-- lokale Kopien
    ++x; <-- global
    }

    void bar()
    {
    int a=3,b=0; <-- lokale Kopien
    b=a*x; <-- hängt auch vom globalen zustand ab
    }

    der code war jetzt C, aber du kannst dir leicht denken, wie das ganze in C# auszusehen hat.



  • Jo Danke, also das mit lokal und global ist ja klar (Grundwissen). Dann danke ich dir für die Auskunft auf meine Frage.



  • werkla4 schrieb:

    Jo Danke, also das mit lokal und global ist ja klar (Grundwissen).

    Echt? Wieso fragst du dann?



  • int[] Zahlenkombination = new int[2] { 0, 0 };
                bool Berechnen = true;
    
                Thread t1 = new Thread(() => Fertig()); t1.Start();
                Thread t2 = new Thread(() => Fertig()); t2.Start();
    
                while (Berechnen)
                {
                    if (Raus(Zahlenkombination, Min.Length))
                        Berechnen = false;
                    KomboErhöhen(ref Zahlenkombination, Min.Length);
                    if (Berechnen)
                    {
                        t1.Join();
                        t1 = new Thread(() => Berechnung(Zahlenkombination,1));
                        t1.Start();
                    }
                    if (Raus(Zahlenkombination, Min.Length))
                        Berechnen = false;
    
                    KomboErhöhen(ref Zahlenkombination, Min.Length);
                    if (Berechnen)
                    {
                        t2.Join();
                        t2 = new Thread(() => Berechnung(Zahlenkombination,2));
                        t2.Start();
                    }
                }
    
                t1.Join(); t2.Join(); // Fertig
    

    In KomboErhöhen werden Zahlenkombinationen hochgezählt z.B.:
    0,1
    0,2
    0,3
    0,4
    0,5
    0,6
    0,7
    0,8
    0,9
    0,10
    ...
    Fertig (Dies Ermittelt Raus())

    Im Einzelschritt Debuggung funktioniert alles tadellos! Wahrscheinlich da er beim langsamen ausführen des Programms schon lange fertig ist. Aber in kompilierter Form führt er es nicht ordentlich aus 😞

    als Test schreiben mir die Methoden die Zahlenkombinationen in eine csv.datei

    Leider kommt dies heraus beim Ausführen des Programms
    Thread1:0,1;0,3;0,4;0,6;0,7;0,8
    Thread2:0,2;0,3;0,4;0,5;0,7;0,9

    Aber theoretisch möchte ich gerne das keine Zahlen doppelt sind! Es darf keine Zahl von Thread 1 in Thread 2 sein und andersherum! Aber es sollten alle Zahlenkombos bis zum Schluss gezählt dabei sein!

    Jemand Tipps für mich?

    P.s. Das mit dem Grundwissen hatte ich nicht so gemeint! Sorry! Sonst hätte ich ja nicht hier Nachgefragt. 😕 😞



  • Ja das sieht wild aus. Aber zeige erstmal die restlichen Methoden.

    Hast Du Dir mal das Tutorial angesehen, v.a. die Synchonisations-Mechanismen?



  • werkla4 schrieb:

    Im Einzelschritt Debuggung funktioniert alles tadellos! Wahrscheinlich da er beim langsamen ausführen des Programms schon lange fertig ist. Aber in kompilierter Form führt er es nicht ordentlich aus 😞

    Der Debugger zeigt dir vermutlich den Ablauf den Threads der Main. Wenn du einen Schritt weiter klickst, dann vergeht eine laengere Zeit, in der die anderen Threads ausgefuehrt werden. Dadurch zwingst du dem System eine Reihenfolge auf!

    Es ist lobenswert, dass du das Ganze lernst, jedoch hast vermutlich den Kern wirklich verstanden. Du kannst naemlich kein paralleles System ueberwachen, wenn du dir nur einen Strang anschaust.

    Wie das zustande kommt koenntest du dir veranschaulichen, indem du Ausgaben ueber die Konsole machst.

    Beispiel: Wenn der Main Thread einen Thread startet machst du eine Ausgabe und jedes mal, wenn der Thread dann ausgefuehrt wird macht er auch eine Ausgabe. Damit wuerdest du sehen, in welcher Reihenfolge es ablaeuft (die Reihenfolge kann bei jedem Programmstart variieren!!!).

    Aber schauen wir uns kurz an, was bei dir schief laeuft. Nehmen wir dein Ergebnis:

    werkla4 schrieb:

    Leider kommt dies heraus beim Ausführen des Programms
    Thread1:0,1;0,3;0,4;0,6;0,7;0,8
    Thread2:0,2;0,3;0,4;0,5;0,7;0,9

    Der Hauptthread legt in Zeile 1 ein Objekt an.
    In Zeile 15 und 16 wird die Referenz auf das Objekt an einen neuen Thread gegeben und dieser wird gestartet.
    Nun wechselt das OS den Thread und dein erster Thread kommt dran, liest den Wert im Objekt und gibt diesen aus. Danach stirbt er und der Hauptthread kommt wieder dran.
    Der Hauptthread veraendert den Wert im Objekt und macht das ganze Spiel mit Thread 2 noch einmal. (Das ist deine zweite Ausgabe.)

    Nun veraendert der Hauptthread wieder den Wert und startet diesmal beide Threads, bevor er die CPU abgiebt und die anderen beiden Threads dran laesst. Die lesen das zur gleichen Zeit aus dem selben Objekt und entnehmen den selben Wert. Und da haben wir dann deinen Fehler.

    Dein Hauptproblem ist, dass du einen sequentiellen Ablauf parallel loesen moechtest und das wird solange schief gehen, solange du die parallelen Ablaeufe nicht dazu zwingst sequentiell zu arbeiten.



  • int[] Zahlenkombination = new int[2] { 0, 0 };
                bool Berechnen = true;
                int[] Kombo1, Kombo2;
    
                Thread t1 = new Thread(() => Fertig()); t1.Start();
                Thread t2 = new Thread(() => Fertig()); t2.Start();
    
                while (Berechnen)
                {
                    if (!t1.IsAlive)
                    {
                        if (Raus(Zahlenkombination, Min.Length))
                            Berechnen = false;
    
                        KomboErhöhen(ref Zahlenkombination, Min.Length);
                        if (Berechnen) //  Thread1
                        {
                            t1.Abort();
                            t1.Join();
                            Kombo1 = new int[] { Zahlenkombination[0], Zahlenkombination[1] };
                            t1 = new Thread(() => Berechnung(Kombo1, 1));
                            t1.Start();
                        }
                    }
                    if (!t2.IsAlive)
                    {
                        if (Raus(Zahlenkombination, Min.Length))
                            Berechnen = false;
    
                        KomboErhöhen(ref Zahlenkombination, Min.Length);
                        if (Berechnen) //  Thread2
                        {
                            t2.Abort();
                            t2.Join();
                            Kombo2 = new int[] { Zahlenkombination[0], Zahlenkombination[1] };
                            t2 = new Thread(() => Berechnung(Kombo2, 2));
                            t2.Start();
                        }
                    }
                    Thread.Sleep(10);
                }
    

Anmelden zum Antworten