C# Maximal 20 Threads zum Verarbeiten verwenden

max_1234

Captain
Registriert
Feb. 2012
Beiträge
3.682
Bin etwas neu im Multithreading und komme gerade nicht weiter.
Das Konzept habe ich verstanden, nur in der Umsetzung gibts noch keine Praxis :)

Ich lese eine Textdatei aus und übergebe den Inhalt in ein String-Array.
Dieses String Array soll nun mit X gleichzeitigen Threads bearbeitet werden und in einer weiteren Methode einen Output liefern.

Hier mal mein sehr stark vereinfachter Code:
Code:
        public void fetchFile()
        {
            //assign file content to string array
            string appPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            string[] rawdata = File.ReadLines(appPath + @"\" + "rawdata.txt").ToArray(); //"

            //loop through array
            for (int i = 0; i < rawdata.Length; i++)
            {
                //simple parameters, e.g. taken from rawdata array
                string a = "aaa", b = "bbb";

                Thread nthread = new Thread(() => WorkerMethod(a, b));
                myNewThread.Start();
            }

        }
        public void WorkerMethod(string a, string b)
        {
            //new objects does get created and method "WorkData" does get called
            Workerclass a = new Workerclass();
            string result = a.WorkData(a, b); //WorkData returns string
            Console.WriteLine(result);
        }

Ich erstelle mit jedem Thread in der Methode "WorkerMethod" das Object "WorkerClass" und rufe in diesem Object die Methode "WorkData" mitsamt Paremetern auf, welche mir dann entsprechende Daten zurückgeben, die dann via WriteLine auf der Konsole ausgegeben werden.

Ich habe schon ein bisschen experimentiert um mehrere Threads zu erstellen .. Aber kam immer Blödsinn dabei raus (duplicated entries/missing data/etc.).
Kann mir da jemand nen Klapps geben?

mfg,
Max
 
Zuletzt bearbeitet:
Der Klapps heißt Synchronisierung.
Schau dir Konzepte des Lockings an. Stichworte dazu sind Mutex und Critical Section. Ich hoffe ich konnte dir weiterhelfen.
 
Wieso sollte es sich hierbei um "Critical Sections" handeln wenn ich in einer for-Schleife die finalisierten zu bearbeitenden Datensätze übergebe? Man sieht hoffentlich dass jedes mal nur eine Zeile bearbeitet werden soll, ansonsten besser ich mein Beispiel noch etwas auf.

Naja, ich habe auf ein paar Erfahrungstechniken in Richtung ThreadPool etc. gehofft.
So(/nst) muss ich die Threads entsprechend einer externen Variable prüfen.
Im obrigen Beispiel ist es ja "leider" so, dass für jede Zeile ein eigener Thread erstellt wird und nicht etwa nur 20.
 
Zuletzt bearbeitet:
max_1234 schrieb:
Im obrigen Beispiel ist es ja "leider" so, dass für jede Zeile ein eigener Thread erstellt wird und nicht etwa nur 20.
Prinzipiell musst du dich schon selbst darum kümmern, wie du deine Daten aufteilst. Warum liest du nicht einfach z. B. 20 Zeilen ein und startest erst damit einen neuen Thread, wenn du das so willst?

Es gibt natürlich mittlerweile deutlich bessere Methoden als die Thread-Klasse um Multithreading zu betreiben. Mittlerweile würde ich nur noch die TPL verwenden, da deutlich performanter und einfacher zu bedienen. Entweder mit einem Task oder in deinem konkreten Fall könnte auch Parallel.ForEach interessant sein.
 
Das klingt interessant ... werd ich mir mal ansehn!
Bei etwa 2k Zeilen zum Bearbeiten wird das generell noch interessant bzgl. der Performance :)
 
Ohne genauer zu wissen was du mit den augelesenen String im String-Array machst, will ich eine kleine Hinweis/Warnung aussprechen:
Teste gründlich mittels kleinen Benchmarks, ob sich das Parallelisieren lohnt! In den meisten Fällen lohnt sich der Aufwand nicht.

Auch halte ich aus dem Bau heraus die 20 Threads als übertrieben. Das Koordinieren und (softe) Kontextwechseln wird mit sehr hoher Wahrscheinlichkeit mehr kosten als es in einem Thread laufen zu lassen. Den Prozess nicht im Main-Thread laufen zu lassen, wird die dagegen mehr bringen, sprich die Verarbeitung in einem einzigen Thread laufen zu lassen.

Vielleicht kannst du etwas detailierter auf deinen Anwendungsfall eingehen?!
 
Das ist richtig. In der Regel brauchst Du nur so viele Threads bis der Prozessor 100% ausgelastet ist. Bei rechenintensiven Vorgängen reichen bei 4 Cores z.B. 4 Threads. Wenn die Threads nur sehr kurz laufen lohnt es in der Regel nicht, auch 2000 Zeilen Code können schnell laufen. Jeder Thread bringt Verwaltungsoverhead mit sich, es ist sehr genau zu beobachten ob Threading wirklich einen Performance vor teil bringt. Zumal Threading auch nicht immer einfach zu beherrschen und zu debuggen ist wie Du schon festgestellt hast...
 
Alle ausgelesenen Daten werden einzeln an ein PHP-Interface gesendet, wobei auf eine Antwort gewartet wird.
Dies mit nur "einem" Thread (also ohne Multithreading) zu erledigen dauerte in mehreren Tests etwa 15-20 Sekunden.
Mit einem Thread/Datensatz komme ich auf <5 Sekunden für 100 Datensätze, wobei ich sagen muss dass hier das System kurz einfriert (was auch nachvollziehbar ist, bei 50 Threads(für 50 Datensätze) die arbeiten).

Ich mach es wahrscheinlich so:
- Threadcounter in der For-Schleife
- Wenn Threadcounter == X, dann wird auf den zuletzt erstellten Thread gewartet (.Join)

Ich bin da leider ziemlich chaotisch. Sobald das irgendwie funktioniert werd ich es vermutlich nie wieder angreifen ;)

mfg,
Max
 
Du könntest auch auf den .Net-Thread-Pool zurückgreifen. Dieser stellt die Threads bereit, die schon erstellt sind und nur noch mit Daten "gefüttert" werden müssen.
Was wohl im Moment dein größtes Performance Problem ist, ist dass du in jedem Schleifen durchlauf ein neues Thread-Objekt erstellst. Das dauert entsprechend lange und dann bringen dir auch 50 Threads nichts.

50 Threads sind sogar eher kontraproduktiv. Daher wohl auch der kurze Freeze. Außerdem sind 50 Threads mMn etwas zu viel. Bei 2000 Zeilen hat ein Thread 40 Zeilen zu bearbeiten. Ich weiß nicht genau was du damit machst, aber es wird wohl eher egal sein ob nun 40 Zeilen pro Thread oder 200 Zeilen pro Thread bearbeitet werden. Das Kontextwechseln und Thread-Erstellen nimmt wohl mehr Zeit in Anspruch. Außerdem wollen die 50 Threads auch mal auf die CPU kommen. Thread-Pooling ist wie gesagt das Stichwort.

Du könntest dir also einen eigenen Thread-Pool schreiben. Ein simpler Thread-Pool ist recht einfach. Einfach eine Anzahl von Threads vorerstellen, einer Liste hinzufügen, eine Queue für anfallende Aufgaben erstellen, alle Threas starten (laufende Endloschleife, die auf ein Signal von außen wartet, dass Daten zum abarbeiten da sind, die Daten dann aus der Queue lädt und aberbeitet), fertig. Hier mal ein Beispiel:

Code:
  public delegate void TaskCallback(object state);

   public class ThreadQueue<T> {

        private object locker = new object();
        private List<Thread> threadList;
        private Queue<T> taskQueue;
        private TaskCallback callback;

        public ThreadQueue(int threadCount, TaskCallback callback) {
            this.callback = callback; //methoenzeiger auf die methode, die ausgeführt werden soll, wenn ein neues 
                                                 // taskObject zur queue hinzugefügt wird
            threadList = new List<Thread>(threadCount);
            taskQueue = new Queue<T>();

            for (int i = 0; i < threadCount; i++) {
                Thread thread = new Thread(Run);
   
                threadList.Add(thread);
                t.Start();
            }

        }

        public void AddTask(T taskObject) {
            lock (locker) {
                
                taskQueue.Enqueue(taskObject);
                Monitor.PulseAll(locker);
            }
        }


        private void Run() {
            while (true) {
             
                lock (locker) {
                    while (taskQueue.Count == 0) {
                        Monitor.Wait(locker);
                    }
                   T taskObject = taskQueue.Dequeue();

                    if (taskObject == null) {
       
                         return;
                    }

                   callback(taskObject);
                }

            }
        }

    }


Hier mal ein besipiel zur Verwendung (der Code gibt einfach nur eine Zahl auf der Console aus, aber in einem Thread aus dem Thread-Pool)

Code:
     public void ThreadQueueTest() {
           
            ThreadQueue<int> tQueue = new ThreadQueue<int>(20, ThreadMethod);
            for (int i = 0; i < 100; i++) {
                tQueue.AddTask(i);
            }
            
        }

        public void ThreadMethod(object taskObject) {
            int? i = taskObject as int?;
            Console.WriteLine("ThreadMethod "+i);
        }


Das war ein Beispiel für einen ziemlich einfachen Thread-Pool. Nachteil: Man kann in diesem Thread-Pool immer nur die selbe Methode ausführen (will man eine andere Methode, braucht man einen extra Thread-Pool)
ich würde dir eher raten den .Net-Thread-Poolzu verwenden. Da musst du dich eigentlich um nichts mehr kümmern. Außerdem ist der .Net-Thread-Pool flexibler (siehe "Nur eine Methode pro Pool" im obigen Beispiel).
 
Zuletzt bearbeitet:
Merci für das Beispiel!
Ich hab mir die .net ThreadPools gestern bereits angekuckt.
Mit meiner Implementierung hatte ich zwar erfolg, nur erschien es mir nicht sehr performant.

Ich werde heute Abend nochmal die deinige testen, unterscheidet sich doch ein ganzes Stück.
Bisweilen habe ich selbst eine Methode für die aktiven Threads implementiert (wie in meinem letzten Beitrag angegeben).

Diese funktioniert sehr einfach und stellt sicher, dass immer maximal X threads aktiv sind. Hier erstelle ich für jede Abfrage dennoch einen eigenen Thread.
Da der Thread sowieso auf die Antwort des Servers warten muss würde es auch nicht schneller gehn wenn ein einzelner Thread mehrere Aufgaben erledigt, insofern habe ich hierbei höchstens einen etwas erhöhten "Overhead". Hierbei zählt nur die Parallelität/Gleichläufigkeit von möglichst vielen, aber niemals "zuvielen" Threads.

Nichtsdestotrotz werde ich deine Implementierung heute Abend testen und mich dann erneut melden.
Bis dahin schon mal danke dass du deine Erfahrungswerte teilst!

mfg,
Max
 
Zuletzt bearbeitet:
TheCadillacMan hat dir schon den richtigen Tipp gegeben. Das ganze ist ein mit Hilfe der Task Parallel Library ziemlich einfach:
Code:
static void FetchFile(string path)
{
    Parallel.ForEach(File.ReadLines(path), DoWork);
}

static void DoWork(string s)
{
    //TODO: whatever
}
Dabei werden Threadpool-Threads verwendet. Die Anzahl der Threads wird anhand der verfügbaren CPU-Kerne bestimmt.
Wenn du unbedingt ein festes Limit haben willst (was in deinem Fall imho nicht nötig sein dürfte), kannst du per ParallelOptions.MaxDegreeOfParallelism die maximale Anzahl der Threads festlegen.
 
Zuletzt bearbeitet:
max_1234 schrieb:
Ich bin da leider ziemlich chaotisch. Sobald das irgendwie funktioniert werd ich es vermutlich nie wieder angreifen ;)

mfg,
Max

Das ist schon die falsche Einstellung. Ich wette Du wirst es irgendwann wieder anpacken müssen und wenn es dann nicht durchschaubar ist wirst Du Dir in den Hintern beißen. :)

Mit dem Threadpool wär ich etwas vorsichtig, das Framework nutzt den selbst auch(WCF Requests z.B.) und wenn Du den z.B. zu sehr einschränkst schießt Du Dir selbst ins Knie. Und Du weißt auch nicht wieviel Threads das System evtl beansprucht. Ich hab mir irgendwann nen eigenen Threadpool geschrieben über den ich dann die volle Kontrolle habe, das ist kein Hexenwerk...
 
@Drexel
Sowas ähnliches hab ich ja aktuelle auch implementiert, eben nur as simple as possible :)

@Grantig
Das kannte ich noch überhaupt nicht, wow. Werde das direkt heute Abend antesten!

Danke für alle Empfehlungen!

mfg,
Max
 
Zurück
Oben