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).
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é.
J'ai donc décidé d'écrire des tests pour cela et de le voir sur des données pratiques.
Légende de test
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é
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.
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.
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.
Vous pouvez affecter votre propre planificateur de tâches à une tâche TPL. Le comportement par défaut work stealing one est assez intelligent.
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";)
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.