myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » Code-Reviews » Threads mit Delegate-Funktion in einer Schleife benutzen Zählvariable gleichzeitig
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Threads mit Delegate-Funktion in einer Schleife benutzen Zählvariable gleichzeitig

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
ByteDevil ByteDevil ist männlich
myCSharp.de-Mitglied

avatar-4066.png


Dabei seit: 02.03.2013
Beiträge: 123
Entwicklungsumgebung: VS 2019 Pro


ByteDevil ist offline

Threads mit Delegate-Funktion in einer Schleife benutzen Zählvariable gleichzeitig

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo liebe Community,

ich hoffe ihr könnt mir auch diesmal wieder helfen...ich hab ein großes Verständnisproblem und war schon drauf und dran den Exorzisten wegen meines PC's zu rufen :/

Ich habe ein Programm geschrieben das Fraktale generiert. Das wollte ich nun beschleunigen, indem ich die Berechnung auf mehrere Threads verteile. Dabei passiert etwas das ich mir beim besten Willen nicht erklären kann. Ich habe mal einen kurzen Beispielcode geschrieben, der den Fehler in wenigen Zeilen reproduziert. Auf dem Screenshot seht ihr wie er sich verhält. Hier auch nochmal für Copy & Paste:

C#-Code:
using System;
using System.Threading;

namespace Multithread_test
{
    class Program
    {
        static int[,] matrix;
        static int runningThreads = 0;

        static void Main(string[] args)
        {
            matrix = new int[1000, 1000];

            for (int y = 0; y < 1000; y++)
            {
                Thread thread = new Thread(delegate () { CalcRow(y); });
                thread.Start();
                runningThreads++;
            }

            while (runningThreads > 0)
                Thread.Sleep(10);

            Console.WriteLine("Fertig!");
            Console.ReadKey();
        }

        static void CalcRow(int y)
        {
            Console.WriteLine("Bearbeite Zeile " + y);
            for (int x = 0; x < 1000; x++)
                matrix[x, y] = x * y;
            runningThreads--;
        }
    }
}

Ich probiere nun schon seit Stunden rum...wie kann es sein das mehrere Threads mit dem gleichen Parameter für y aufgerufen werden? Auch wird die Methode mit y-Werten über 999 aufgerufen, was doch laut der Abbruchbedingung der Schleife gar nicht sein kann!?
Einige Zeilen werden auch einfach übersprungen und gar nicht berechnet.

Bitte erleuchtet mich...

Viele Grüße,
ByteDevil

ByteDevil hat dieses Bild (verkleinerte Version) angehängt:
Multithread bug.png
Volle Bildgröße

Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von ByteDevil am 28.08.2016 00:11.

27.08.2016 23:47 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Palladin007 Palladin007 ist männlich
myCSharp.de-Mitglied

avatar-4140.png


Dabei seit: 03.02.2012
Beiträge: 1.362
Entwicklungsumgebung: Visual Studio 2019
Herkunft: NRW


Palladin007 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Du hast eine gemeinsame y-Variable für jeden Thread hast.
Wenn Du den 1000ten Thread mit y=999 gestartet hast, dann dauert es einen kurzen Moment bis der Thread und damit der Delegat gestartet wird. In der Zeit hat die for-Schleife aber schon weiter gezählt und steht auf 1000. Der Code in der Schleife wird nicht ausgeführt, weil die Bedingung nicht erfüllt ist (kleiner als 1000), dennoch ist der Wert, der in dem letzten Thread verwendet wird, 1000.

Deshalb überspringt er auch y=0, denn der Thread, der y=0 bearbeiten soll, läuft erst an, wenn die for-Schleife schon weiter gezählt hat.

Es reicht schon, wenn Du den Wert in eine Variable zwischenspeicherst. Diese Zwischen-Variable wird von der for-Schleife nicht behandelt.
Besser wäre aber, wenn Du Parallel.For verwendest, das tut ungefähr genau das, was Du willst.

Beachte aber auch: Das Starten eines Threads kostet Zeit und das nicht wenig. Du erstellst für jeden Durchlauf einen Thread und jedes mal kostet das viel Zeit.
Parallel.For kann die Aufgaben auf wenige Threads aufteilen. Begrenzt Du auf vier Threads, dann führt jeder der vier Threads 250 Berechnungen durch, das ist viel effektiver.

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Palladin007 am 28.08.2016 00:24.

28.08.2016 00:23 Beiträge des Benutzers | zu Buddylist hinzufügen
ByteDevil ByteDevil ist männlich
myCSharp.de-Mitglied

avatar-4066.png


Dabei seit: 02.03.2013
Beiträge: 123
Entwicklungsumgebung: VS 2019 Pro

Themenstarter Thema begonnen von ByteDevil

ByteDevil ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Wie meinst du das sie teilen sich die y-Variable? Die in CalcRow und der Main-Methode haben doch nichts miteinander zu tun. Auch wenn ich y in CalcRow umbenenne, ändert das nichts an dem Verhalten.

Wie kann ich denn Parallel.For auf eine methode mit Parameter anwenden?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von ByteDevil am 28.08.2016 00:33.

28.08.2016 00:32 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Palladin007 Palladin007 ist männlich
myCSharp.de-Mitglied

avatar-4140.png


Dabei seit: 03.02.2012
Beiträge: 1.362
Entwicklungsumgebung: Visual Studio 2019
Herkunft: NRW


Palladin007 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Du hast aber noch einen Delegaten dazwischen, erst darin wird deine Methode aufgerufen und darin wird auch die y-Variable verwendet. Und dieser Delegat wird aufgerufen, nachdem die for-Schleife weiter gezählt hat.
28.08.2016 00:34 Beiträge des Benutzers | zu Buddylist hinzufügen
ByteDevil ByteDevil ist männlich
myCSharp.de-Mitglied

avatar-4066.png


Dabei seit: 02.03.2013
Beiträge: 123
Entwicklungsumgebung: VS 2019 Pro

Themenstarter Thema begonnen von ByteDevil

ByteDevil ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke Palladin007 :)

Ich habe deinen Rat beherzigt und einfach zu beginn der Schleife den Schleifenzähler zwischengespeichert. Nun funktioniert es perfekt und die Berechnung läuft fast 10 mal so schnell wie mit nur einem Thread :)

Liebe Grüße,
ByteDevil
28.08.2016 01:20 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
steffen_dec steffen_dec ist männlich
myCSharp.de-Mitglied

avatar-701.gif


Dabei seit: 27.03.2007
Beiträge: 322
Entwicklungsumgebung: VS2015 Ent


steffen_dec ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

hier noch ein Beispiel mit Parallel.For:

C#-Code:
internal class Program
    {
        private static int[,] matrix;

        private static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();
            matrix = new int[1000, 1000];

            Parallel.For(0, 1000, y =>
            {
                CalcRow(y);
            });

            Console.WriteLine("Fertig! nach " + watch.ElapsedMilliseconds.ToString("0 ms"));
            Console.ReadKey();
        }

        private static void CalcRow(int y)
        {
            Console.WriteLine("Bearbeite Zeile " + y);
            for (int x = 0; x < 1000; x++)
                matrix[x, y] = x * y;
        }
    }
28.08.2016 09:06 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
chilic
myCSharp.de-Poweruser/ Experte

Dabei seit: 12.02.2010
Beiträge: 2.056


chilic ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von ByteDevil:
war schon drauf und dran den Exorzisten wegen meines PC's zu rufen

Verständlich :-)

An sowas bin ich auch schon hängen geblieben. Das ist ordentlich böse. Mir hat es geholfen sich das so vorzustellen.
Du sagst jedem Thread, wenn du startest nutze den Wert y.
Jetzt läuft deine Schleife durch und legt die Threads an. Wann der Thread losläuft ist nicht bestimmbar, jedenfalls nicht direkt sofort. Erst mal darf die Schleife noch weiterlaufen. Wann der Thread dann in y nachsieht ist auch wieder unklar.
Im Extremfall läuft erst deine Schleife komplett durch und erst dann werten die Threads y aus. Dann sehen alle Threads den selben Wert.
In der Realität wird es so sein dass die Schleife ein paar Werte hochzählt, dann kommen ein paar wartende Threads an die Reihe die nun alle den selben Wert lesen, dann wieder die Schleife...

Jetzt kommt noch das Sahnehäubchen der Verwirrung. Die Schleife zählt y nicht bis 999 hoch sondern bis 1000, da bricht sie dann ab. Das erklärt warum Threads die nach dem Schleifenende y auslesen die Zahl 1000 sehen.
Mit der Zwischenvariable wird für jeden Thread eine Kopie des Werts angelegt, dann schaut jeder in seine eigene Kopie deren Wert sich nicht mehr ändert.
28.08.2016 09:36 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
ByteDevil ByteDevil ist männlich
myCSharp.de-Mitglied

avatar-4066.png


Dabei seit: 02.03.2013
Beiträge: 123
Entwicklungsumgebung: VS 2019 Pro

Themenstarter Thema begonnen von ByteDevil

ByteDevil ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ja danke euch :) Jetzt hab ich es kapiert. War mir nicht bewusst das dort solch eine Verzögerung im Gange ist.

Bei diesem kleinen Beispielcode ist es tatsächlich so, das die Berechnung sogar langsamer wird wenn ich für jede der Zeilen einen neuen Thread starte...war mir aber klar das es mit Kanonen auf Spatzen schießen ist. Das war wie gesagt nur als Beispiel. Meine echte Berechnung ist aufwändiger und dort habe ich tatsächlich die benötigte Zeit pro Berechnung um fast 85% reduzieren können...denn vorher schlief meine CPU fast ein^^

Aber auch unbegrenzt viele Threads öffnen zu lassen war nicht so optimal. Weniger ist auch hier teilweise mehr. Hab es nun so gemacht, dass maximal so viele Berechnungs-Threads gestartet werden, wie CPU-Kerne vorhanden sind (in meinem Fall 12).

Nochmal ein dickes Danke an alle :)

Grüße,
ByteDevil

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von ByteDevil am 28.08.2016 10:04.

28.08.2016 10:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
chilic
myCSharp.de-Poweruser/ Experte

Dabei seit: 12.02.2010
Beiträge: 2.056


chilic ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von ByteDevil:
Hab es nun so gemacht, dass maximal so viele Berechnungs-Threads gestartet werden, wie CPU-Kerne vorhanden sind (in meinem Fall 12).

Gute Entscheidung. 12 Kerne können nur 12 Dinge gleichzeitig tun. Wenn man dir mehr als eine Aufgabe gleichzeitig zu tun gibt, wirst auch du nur langsamer statt schneller :-)
28.08.2016 10:38 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Th69
myCSharp.de-Poweruser/ Experte

avatar-2578.jpg


Dabei seit: 01.04.2008
Beiträge: 3.799
Entwicklungsumgebung: Visual Studio 2015/17


Th69 ist online

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo ByteDevil,

als Ergänzung (bzgl. des Verhaltens bei der Variable y) noch für dich die Stichworte: Closure sowie Captured Variable, s. z.B.  Closures and Captured Variable C#
28.08.2016 12:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 14.280
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Bitte macht niemals Performance-Vergleiche mit Buffer-Outputs wie Console.Writeline.
Das verfälscht jeglichen Vergleich und verlangsamt die Ausführung um das xx Fache!

Und vergesst bitte nicht: Premature optimization is the root of all evil.
Aber es kann sich bei Parallel Programming schnell lohnen. Und ihr wisst ja: performance is a feature!

Tipps bei Paralleler Verarbeitung:
- Arbeitet mit Tasks statt mit Threads. Threads haben ihre Vorteile; kommen aber nur bei pauschalen <1% in C# Situationen wirklich zum Tragen. Sie lassen sich einfach viel leichter als Entwickler anwenden.
- Niemals Kaskadieren. Das beschränkt euch künstlich und es wird langsamer. Verwendet die parallele Schleife wenn möglich in der Hierachieebene oben!
- Wenn int x == y dann verwendet alles in einer Schleife (Parallel.For) - vielfaches schneller!
- Es kann schneller sein, wenn ihr die Liste zu iterierenden Objekte bereits aufgebaut habt. Das macht es dem Scheduler hinter Parallel.For/Foreach leichter! Parallel.For vs. Parallel.ForEach kann schneller sein, wenn Hinter ForEach noch der Enumerator was feuern muss.
- Vermeidet wenn möglich locking innerhalb der parallelen Abarbeitung. Legt euch hier wenn möglich eine lokale Variable an und gibt das Resultate zurück. Das braucht zwar kurz mehr Speicher; aber die Ausführung wird nicht künstlich verlangsamt.

Deswegen ist:

C#-Code:
            var matrix = new int[1000, 1000];
            Parallel.ForEach(Enumerable.Range(0,1000), y =>
            {
                for(int x = 0;x < 1000;x++)
                    matrix[x, y] = x * y;
            });

meist, aber nicht immer schneller als:

C#-Code:
            var matrix = new int[1000, 1000];
            Parallel.For(0, 1000, y =>
            {
                for(int x = 0;x < 1000;x++)
                    matrix[x, y] = x * y;
            });

Der Faktor wird bestimmt 2-3 betragen!

Hier mal Basiscode, mit dem jeder selbst vergleichen kann.
Mein Dual Xeon mit 16 logischen Prozessoren wird andere Resultate liefern als zB. ein i5 Dual Core und 4 logischen Prozessoren.

C#-Code:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace mycsharp.Examples.ParallelVsParallel
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Comparison");

            var runs = 50;

            var runParallelForSeqFirst = Messure(RunWithParallelForSeqFirst, runs);

            var runParallelForParFirst = Messure(RunWithParallelForParFirst, runs);

            var runParallelForXandYSame = Messure(RunWithParallelForXandYSame, runs);

            var runParalellForeach = Messure(RunWithParallelForeach, runs);


            Console.WriteLine($"== Results of {runs} runs");
            Console.WriteLine($" >> Parallel For Seq First: {runParallelForSeqFirst} in total");
            Console.WriteLine($" >> Parallel For Par First: {runParallelForParFirst} in total");
            Console.WriteLine($" >> Parallel For X and Y same: {runParallelForXandYSame} in total");
            Console.WriteLine($" >> Parallel Foreach: {runParalellForeach} in total");

            Console.ReadKey();
        }

        private static Int64 Messure(Action action, int runCount)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for(int i = 0;i < runCount;i++)
            {
                action();
            }
            return sw.ElapsedMilliseconds;
        }


        public static void RunWithParallelForParFirst()
        {
            var matrix = new int[1000, 1000];
            Parallel.For(0, 1000, y =>
            {
                for(int x = 0;x < 1000;x++)
                    matrix[x, y] = x * y;
            });
        }
        public static void RunWithParallelForXandYSame()
        {
            var matrix = new int[1000, 1000];
            Parallel.For(0, 1000, y =>
            {
                var x = y;
                matrix[x, y] = x * y;
            });
        }

        public static void RunWithParallelForSeqFirst()
        {
            var matrix = new int[1000, 1000];
            for(int x = 0;x < 1000;x++)
            {
                var thisX = x; // modified closure
                Parallel.For(0, 1000, y =>
                {
                    matrix[thisX, y] = thisX * y;
                });
            }
        }

        public static void RunWithParallelForeach()
        {
            var matrix = new int[1000, 1000];
            Parallel.ForEach(Enumerable.Range(0, 1000), y =>
            {
                for(int x = 0;x < 1000;x++)
                    matrix[x, y] = x * y;
            });

        }
    }
}
28.08.2016 14:05 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 4 Jahre.
Der letzte Beitrag ist älter als 4 Jahre.
Antwort erstellen


© Copyright 2003-2020 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 23.11.2020 17:52