web-dev-qa-db-fra.com

Dois-je utiliser ThreadPools ou Task Parallel Library pour les opérations liées à l'IO?

Dans l'un de mes projets qui est un peu un agrégateur, j'analyse des flux, des podcasts et ainsi de suite sur le Web.

Si j'utilise une approche séquentielle, étant donné le grand nombre de ressources, le traitement de toutes les ressources prend beaucoup de temps (à cause de problèmes de réseau et autres problèmes similaires);

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

Je voulais donc implémenter la concurrence et je ne pouvais pas décider si je devais utiliser essentiellement ThreadPools pour traiter avec des threads de travail ou simplement compter sur TPL pour le trier.

ThreadPools assurera certainement le travail pour moi avec des threads de travail et j'obtiendrai ce que j'attendais (et dans les environnements de processeurs multicœurs, les autres cœurs seront également utilisés).

concurrency

Mais je veux toujours considérer TPL aussi car c'est la méthode recommandée, mais cela me préoccupe un peu. Tout d'abord, je sais que TPL utilise ThreadPools mais ajoute une couche supplémentaire de prise de décision. Je suis principalement préoccupé par la condition selon laquelle un environnement monocœur est présent. Si je ne me trompe pas, TPL commence par un nombre de tâches d'exécution égal au nombre de cœurs de processeur disponibles au tout début. Je crains que TPL ne produise des résultats similaires à ceux d'une approche séquentielle pour mon cas lié à l'IO.

Ainsi, pour les opérations liées aux entrées-sorties (dans mon cas, la lecture de ressources Web), est-il préférable d'utiliser ThreadPools et de contrôler les éléments ou, mieux, de simplement s'appuyer sur TPL? Est-ce que TPL peut également être utilisé dans des scénarios IO-bound?

Mise à jour: Ma principale préoccupation est que - sur un environnement à processeur unique, la TPL se comportera-t-elle comme une approche séquentielle ou offrira-t-elle toujours une concurrence? Je lis déjà Programmation parallèle avec Microsoft .NET et donc le livre mais je n'ai pas trouvé de réponse exacte à cela.

Remarque: il s'agit d'une reformulation de ma question précédente [ Est-il possible d'utiliser la simultanéité des threads et le parallélisme? ] qui était mal formulé.

73
HuseyinUslu

J'ai donc décidé d'écrire des tests pour cela et de le voir sur des données pratiques. 

Légende de test

  • Itr: Itération 
  • Seq: Approche séquentielle. 
  • PrlEx: Extensions Parallèles - Parallel.ForEach 
  • TPL: bibliothèque parallèle de tâches 
  • TPool: ThreadPool

Résultats de test

CPU mono-core [Win7-32] - fonctionne sous 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
________________________________________________________________________________

CPU mono-core [WinXP] - s'exécute sous 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
________________________________________________________________________________

CPU dual-core [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
________________________________________________________________________________

CPU quad-core [Win7-64] - HyprerThreading pris en charge -

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
________________________________________________________________________________

Résumé

  • Que vous fonctionniez dans un environnement monocœur ou multicœur, Parallel Extensions, TPL et ThreadPool se comportent de la même manière et donnent des résultats approximatifs.
  • Toujours TPL a avantages comme la gestion facile des exceptions, le support d'annulation et la possibilité de renvoyer facilement les résultats d'une tâche. Bien que les extensions parallèles soient aussi une autre alternative viable.

Lancer des tests par vous-même

Vous pouvez télécharger le code source ici et le faire vous-même. Si vous pouvez poster les résultats, je les ajouterai aussi.

Mise à jour: Correction du lien source.

101
HuseyinUslu

Si vous essayez d'optimiser le débit des tâches IO, vous devez absolument doit associer les API APM (Asynchronous Processing Model) traditionnelles à votre travail basé sur TPL. Les API APM constituent le seul moyen de débloquer le thread de la CPU tant que le rappel asynchrone IO est en attente. La TPL fournit la méthode d'assistance TaskFactory::FromAsync pour vous aider à combiner le code APM et le code TPL.

Consultez cette section du kit de développement .NET sur MSDN intitulée TPL et la programmation asynchrone .NET traditionnelle pour plus d'informations sur la manière de combiner ces deux modèles de programmation pour obtenir le nirvana asynchrone.

15
Drew Marsh

Vous avez raison de dire que la TPL supprime une partie du contrôle que vous avez lorsque vous créez votre propre pool de threads. Mais ceci n’est correct que si vous ne voulez pas creuser plus profondément. La TPL vous permet de créer des tâches longues qui ne font pas partie du pool de threads TPL et qui pourraient bien vous servir. Le livre publié qui est une lecture gratuite Programmation parallèle avec Microsoft .NET vous donnera beaucoup plus de précisions sur la manière dont la TPL doit être utilisée. Vous avez toujours la possibilité de donner à Paralle.For, Tasks explicit Paramètres le nombre de threads à allouer. En plus de cela, vous pouvez remplacer le planificateur TPL par votre propre si vous souhaitez un contrôle total. 

2
Alois Kraus

Vous pouvez affecter votre propre planificateur de tâches à une tâche TPL. Le comportement par défaut work stealing one est assez intelligent.

1
ehnmark

Je crains que TPL ne produise des résultats similaires à ceux d'une approche séquentielle pour mon cas lié à l'IO.

Je pense que ça va. Quel est le goulot d'étranglement? Est-ce que l'analyse ou le téléchargement? Le multithreading ne vous aidera pas beaucoup à télécharger depuis le Web. 

J'utiliserais Task Parallel Library pour le rognage, l'application de masques ou d'effets pour les images téléchargées, la découpe d'échantillons dans un podcast, etc. C'est plus évolutif.

Mais ce ne sera pas l'ordre de grandeur d'accélérer. Consacrez vos ressources à la mise en œuvre de certaines fonctionnalités et à des tests.

PS. "Wow ma fonction s'exécute en 0,7 s au lieu de 0,9";) 

0
Lukasz Madon

Si vous parallélisez vos appels aux urls, je pense que cela améliorera votre application, même si vous n’avez qu’un seul noyau . Regardez ce code:

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

La différence entre multithreading et asynchronisme dans ce cas est la façon dont le rappel/achèvement est effectué.

Lorsque vous utilisez EAP, le nombre de tâches n'est pas lié au nombre de threads.

Lorsque vous utilisez la tâche GetAsync, le client http utilise un flux de réseau (socket, client tcp ou autre) et le signale pour déclencher un événement lorsque BeginRead/EndRead est terminé. Donc, aucun thread n'est impliqué dans ce moment.

Une fois l'appel terminé, un nouveau thread est peut-être créé, mais il appartient à TaskScheduler (utilisé dans l'appel GetAsync/ContinueWith) de créer un nouveau thread, d'utiliser un thread existant ou de mettre en ligne la tâche pour utiliser le thread d'appel.

Si AnalyzeThisWords bloque trop de temps, vous commencez à avoir des goulots d'étranglement car le "rappel" sur ContinueWith est effectué à partir d'un travailleur de pool de threads.

0