Sollte ich ThreadPools oder Task Parallel Library für IO-gebundene Operationen verwenden?

78

In einem meiner Projekte, das irgendwie ein Aggregator ist, analysiere ich Feeds, Podcasts und so weiter aus dem Internet.

Wenn ich bei einer großen Anzahl von Ressourcen einen sequentiellen Ansatz verwende, dauert es ziemlich lange, alle zu verarbeiten (aufgrund von Netzwerkproblemen und ähnlichen Problemen).

foreach(feed in feeds)
{
   read_from_web(feed)
   parse(feed)
}

Daher möchte ich Parallelität implementieren und konnte mich nicht entscheiden, ob ich ThreadPools grundsätzlich für die Verarbeitung mit Arbeitsthreads verwenden oder mich einfach auf TPL verlassen soll, um sie zu sortieren.

ThreadPools erledigt den Job mit Sicherheit mit Worker-Threads und ich bekomme das, was ich erwarte (und in Multi-Core-CPU-Umgebungen werden auch die anderen Kerne verwendet).

Parallelität

Aber ich möchte immer noch auch TPL als empfohlene Methode in Betracht ziehen, aber ich bin ein bisschen besorgt darüber. Zunächst weiß ich, dass TPL ThreadPools verwendet, aber zusätzliche Entscheidungsebenen hinzufügt. Ich bin hauptsächlich besorgt über den Zustand, in dem eine Single-Core-Umgebung vorhanden ist. Wenn ich mich nicht irre, beginnt TPL mit einer Anzahl von Worker-Threads, die der Anzahl der verfügbaren CPU-Kerne am Anfang entspricht. Ich befürchte, dass TPL für meinen IO-gebundenen Fall ähnliche Ergebnisse wie der sequentielle Ansatz liefert.

Ist es für IO-gebundene Vorgänge (in meinem Fall das Lesen von Ressourcen aus dem Web) am besten, ThreadPools zu verwenden und die Dinge zu steuern, oder sich besser nur auf TPL zu verlassen? Kann TPL auch in IO-gebundenen Szenarien verwendet werden?

Update : Mein Hauptanliegen ist, dass sich TPL in einer Single-Core-CPU- Umgebung nur wie ein sequentieller Ansatz verhält oder weiterhin Parallelität bietet. Ich lese bereits Parallel Programming mit Microsoft .NET und damit das Buch , konnte aber keine genaue Antwort darauf finden.

Hinweis: Dies ist eine Umformulierung meiner vorherigen Frage [ Ist es möglich, Thread-Parallelität und Parallelität zusammen zu verwenden? ] was ziemlich falsch formuliert war.

HuseyinUslu
quelle

Antworten:

107

Also habe ich mich stattdessen entschlossen, Tests dafür zu schreiben und sie anhand praktischer Daten zu sehen.

Testlegende

  • Itr: Iteration
  • Seq: Sequentieller Ansatz.
  • PrlEx: Parallele Erweiterungen - Parallel.ForEach
  • TPL: Task Parallel Library
  • TPool: ThreadPool

Testergebnisse

Single-Core-CPU [Win7-32] - läuft unter VMWare -

Test Environment: 1 physical cpus, 1 cores, 1 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.82s  04.05s  02.69s  02.60s
#2      07.48s  03.18s  03.17s  02.91s
#3      07.66s  03.21s  01.90s  01.68s
#4      07.43s  01.65s  01.70s  01.76s
#5      07.81s  02.20s  01.75s  01.71s
#6      07.67s  03.25s  01.97s  01.63s
#7      08.14s  01.77s  01.72s  02.66s
#8      08.04s  03.01s  02.03s  01.75s
#9      08.80s  01.71s  01.67s  01.75s
#10     10.19s  02.23s  01.62s  01.74s
________________________________________________________________________________

Avg.    08.40s  02.63s  02.02s  02.02s
________________________________________________________________________________

Single-Core-CPU [WinXP] - läuft unter VMWare -

Test Environment: 1 physical cpus, NotSupported cores, NotSupported logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.79s  04.05s  02.75s  02.13s
#2      07.53s  02.84s  02.08s  02.07s
#3      07.79s  03.74s  02.04s  02.07s
#4      08.28s  02.88s  02.73s  03.43s
#5      07.55s  02.59s  03.99s  03.19s
#6      07.50s  02.90s  02.83s  02.29s
#7      07.80s  04.32s  02.78s  02.67s
#8      07.65s  03.10s  02.07s  02.53s
#9      10.70s  02.61s  02.04s  02.10s
#10     08.98s  02.88s  02.09s  02.16s
________________________________________________________________________________

Avg.    08.46s  03.19s  02.54s  02.46s
________________________________________________________________________________

Dual-Core-CPU [Win7-64]

Test Environment: 1 physical cpus, 2 cores, 2 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      07.09s  02.28s  02.64s  01.79s
#2      06.04s  02.53s  01.96s  01.94s
#3      05.84s  02.18s  02.08s  02.34s
#4      06.00s  01.43s  01.69s  01.43s
#5      05.74s  01.61s  01.36s  01.49s
#6      05.92s  01.59s  01.73s  01.50s
#7      06.09s  01.44s  02.14s  02.37s
#8      06.37s  01.34s  01.46s  01.36s
#9      06.57s  01.30s  01.58s  01.67s
#10     06.06s  01.95s  02.88s  01.62s
________________________________________________________________________________

Avg.    06.17s  01.76s  01.95s  01.75s
________________________________________________________________________________

Quad-Core-CPU [Win7-64] - HyprerThreading unterstützt -

Test Environment: 1 physical cpus, 4 cores, 8 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.56s  02.03s  01.71s  01.69s
#2      07.42s  01.63s  01.71s  01.69s
#3      11.66s  01.69s  01.73s  01.61s
#4      07.52s  01.77s  01.63s  01.65s
#5      07.69s  02.32s  01.67s  01.62s
#6      07.31s  01.64s  01.53s  02.17s
#7      07.44s  02.56s  02.35s  02.31s
#8      08.36s  01.93s  01.73s  01.66s
#9      07.92s  02.15s  01.72s  01.65s
#10     07.60s  02.14s  01.68s  01.68s
________________________________________________________________________________

Avg.    08.35s  01.99s  01.75s  01.77s
________________________________________________________________________________

Zusammenfassung

  • Unabhängig davon, ob Sie in einer Single-Core-Umgebung oder einer Multi-Core-Umgebung ausgeführt werden, verhalten sich Parallel Extensions, TPL und ThreadPool gleich und liefern ungefähre Ergebnisse .
  • Dennoch bietet TPL Vorteile wie eine einfache Ausnahmebehandlung, Unterstützung bei der Stornierung und die Möglichkeit, Aufgabenergebnisse einfach zurückzugeben . Parallele Erweiterungen sind jedoch auch eine weitere Alternative.

Tests selbst durchführen

Sie können die Quelle hier herunterladen und selbst ausführen. Wenn Sie die Ergebnisse veröffentlichen können, werde ich sie auch hinzufügen.

Update: Der Quelllink wurde behoben.

HuseyinUslu
quelle
6
+1 Zur Lösung Ihres Problems mit einem wissenschaftlichen Ansatz. Wenn ich mich nicht irre, verwenden alle oben genannten Technologien den Thread-Pool, der die ähnlichen Ergebnisse erklärt. Im Allgemeinen verwende ich gerne parallele Erweiterungen, weil die Syntax so einfach ist und ich faul bin.
Jacobs Data Solutions
Ich habe die Quellen ein wenig gereinigt; github.com/raistlinthewiz/concurrency-tests
HuseyinUslu
1
Vielen Dank, dass Sie die ganze Arbeit für den Test durchlaufen haben.
GregoryBrad
15

Wenn Sie versuchen, den Durchsatz für E / A-gebundene Aufgaben zu maximieren, müssen Sie unbedingt die traditionellen APM-APIs (Asynchronous Processing Model) mit Ihrer TPL-basierten Arbeit kombinieren. Die APM-APIs sind die einzige Möglichkeit, den CPU-Thread zu entsperren, während der asynchrone E / A-Rückruf ansteht. Die TPL bietet die TaskFactory::FromAsyncHilfsmethode zur Unterstützung der Kombination von APM- und TPL-Code.

In diesem Abschnitt des .NET SDK auf MSDN mit dem Titel TPL und traditionelle asynchrone .NET-Programmierung finden Sie weitere Informationen zum Kombinieren dieser beiden Programmiermodelle, um ein asynchrones Nirvana zu erzielen.

Drew Marsh
quelle
2

Sie haben Recht, dass die TPL einen Teil des Steuerelements entfernt, das Sie beim Erstellen Ihres eigenen Thread-Pools haben. Dies ist jedoch nur dann richtig, wenn Sie nicht tiefer graben möchten. Mit der TPL können Sie Aufgaben mit langer Laufzeit erstellen, die nicht Teil des TPL-Thread-Pools sind und Ihren Zweck erfüllen können. Das veröffentlichte Buch, das eine kostenlose gelesene parallele Programmierung mit Microsoft .NET ist, gibt Ihnen viel mehr Einblick, wie die TPL verwendet werden soll. Sie haben immer die Möglichkeit, Paralle.For, Tasks explizite Parameter anzugeben, wie viele Threads zugewiesen werden sollen. Außerdem können Sie den TPL-Scheduler durch Ihren eigenen ersetzen, wenn Sie die volle Kontrolle haben möchten.

Alois Kraus
quelle
1

Sie können einer TPL-Aufgabe Ihren eigenen Aufgabenplaner zuweisen . Die Standardarbeit , die man stiehlt , ist allerdings ziemlich klug.

ehnmark
quelle
Ich habe diese Ressource gelesen und bin deshalb besorgt. In .NET Framework 4 ist der Standard-Taskplaner eng in den Thread-Pool integriert. Wenn Sie den Standard-Taskplaner verwenden, werden die Arbeitsthreads, die parallele Aufgaben ausführen, von der .NET ThreadPool-Klasse verwaltet. Im Allgemeinen befinden sich mindestens so viele Worker-Threads wie Kerne auf Ihrem Computer. - Was ist, wenn ich in einer Single-Core-Umgebung arbeite? Ich kann keine Klarstellungen dazu finden.
HuseyinUslu
1
Sie möchten also in diesem Fall ein Mindestmaß an Parallelität erzwingen , z. B. zuletzt X Grad? TaskCreationOptions.LongRunning könnte sein, was Sie wollen. Ein benutzerdefinierter Planer ist eine weitere Option, wenn Sie den Standardplaner nicht davon überzeugen können, das zu tun, was Sie möchten.
Ehnmark
Eigentlich scheint es, basierend auf den Testergebnissen, die ich gepostet habe, meine Bedenken waren nicht gültig.
HuseyinUslu
0

Ich befürchte, dass TPL für meinen IO-gebundenen Fall ähnliche Ergebnisse wie der sequentielle Ansatz liefert.

Ich denke, es wird. Was ist der Engpass? Wird analysiert oder heruntergeladen? Multithreading hilft Ihnen beim Herunterladen aus dem Internet nicht viel.

Ich würde Task Parallel Library zum Zuschneiden, Anwenden von Masken oder Effekten für heruntergeladene Bilder, Ausschneiden von Beispielen aus Podcasts usw. verwenden. Es ist skalierbarer.

Aber es wird nicht die Größenordnung beschleunigen. Verwenden Sie Ihre Ressourcen, um einige Funktionen zu implementieren und zu testen.

PS. "Wow, meine Funktion wird in 0,7 s statt in 0,9 ausgeführt";)

Lukasz Madon
quelle
Engpass ist das Herunterladen des Inhalts. Ein normaler Benutzer abonniert mehr als 10 Feeds und von meinen Tests dauert es mehr als 15 Sekunden - zumindest für meine Testuntermenge -, um alle Feeds mit sequentiellem Ansatz herunterzuladen und zu analysieren. Ich habe das Gefühl, dass es genug Raum für Verbesserungen gibt.
HuseyinUslu
Basierend auf den Testergebnissen, die ich veröffentlicht habe, scheint es wahr zu sein, dass es genug Raum für Verbesserungen gab.
HuseyinUslu
0

Wenn Sie Ihre Anrufe mit den URLs parallelisieren, wird dies Ihrer Meinung nach Ihre Anwendung verbessern, selbst wenn Sie nur einen Kern haben. Schauen Sie sich diesen Code an:

var client = new HttpClient();
var urls = new[]{"a", "url", "to", "find"};

// due to the EAP pattern, this will run in parallel.
var tasks = urls.Select(c=> client.GetAsync(c));

var result = Tasks.WhenAll(task).ContinueWith(a=> AnalyzeThisWords(a.Result));
result.Wait(); // don't know if this is needed or it's correct to call wait

Der Unterschied zwischen Multithreading und Asynchronität besteht in diesem Fall darin, wie der Rückruf / die Fertigstellung erfolgt.

Bei Verwendung von EAP hängt die Anzahl der Aufgaben nicht mit der Anzahl der Threads zusammen.

Da Sie sich auf die GetAsync-Aufgabe verlassen, verwendet der http-Client einen Netzwerkstrom (Socket, TCP-Client oder was auch immer) und signalisiert ihm, ein Ereignis auszulösen, wenn BeginRead / EndRead abgeschlossen ist. In diesem Moment sind also keine Threads beteiligt.

Nach dem Aufruf der Fertigstellung wird möglicherweise ein neuer Thread erstellt, aber es liegt an TaskScheduler (verwendet im Aufruf GetAsync / ContinueWith), einen neuen Thread zu erstellen, einen vorhandenen Thread zu verwenden oder die Aufgabe inline zu verwenden, um den aufrufenden Thread zu verwenden.

Wenn die AnalyzeThisWordsBlöcke zu lange dauern, treten Engpässe auf, da der "Rückruf" auf ContinueWith von einem Thread-Pool-Worker ausgeführt wird.

Rafael Diego Nicoletti
quelle