2015-09-18 2 views
10

J'ai écrit un programme qui utilise tous les cœurs disponibles en utilisant Parallel.ForEach. La liste pour le ForEach contient ~ 1000 objets et le calcul pour chaque objet prend un certain temps (~ 10 sec). Dans cette configuration scénario I d'une minuterie comme ceci:System.Timers.Timer massivement inexact

timer = new System.Timers.Timer(); 
timer.Elapsed += TimerHandler; 
timer.Interval = 15000; 
timer.Enabled = true; 

private void TimerHandler(object source, ElapsedEventArgs e) 
{ 
     Console.WriteLine(DateTime.Now + ": Timer fired"); 
} 

Au moment où la méthode TimerHandler est un bout pour vous assurer que le problème ne soit pas causé par cette méthode.

Mon attente est que la méthode TimerHandler sera exécutée toutes les 15 secondes ~. Cependant, le temps entre deux appels à cette méthode atteint même 40 secondes, soit 25 secondes de trop. En utilisant new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount -1 } pour la méthode Parallel.ForEach cela ne se produit pas et l'intervalle attendu de 15 secondes est vu.

Est-il prévu que je dois m'assurer qu'il y a toujours un noyau disponible par minuteur actif? Semble être un peu plus bizarre, car le "réservé" pourrait être une ressource précieuse pour mon calcul.

Edit: Comme indiqué par Yuval la fixation d'un minimum fixé de fils dans la piscine par ThreadPool.SetMinThreads résolu le problème. J'ai également essayé new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } (donc sans le -1 dans la question initiale) pour la méthode Parallel.ForEach et cela résout également le problème. Cependant, je n'ai aucune bonne explication pourquoi ces modifications ont résolu le problème. Peut-être qu'il y avait tellement de threads créés que le thread de la minuterie a été "perdu" pendant un temps "long" jusqu'à ce qu'il soit à nouveau exécuté.

+1

Si vous définissez explicitement le nombre de threads dans le pool de threads avant d'exécuter la boucle parallèle, est-ce toujours le cas? (En utilisant 'ThreadPool.SetMinThreads') –

+0

utilisez-vous le processeur lorsque vous utilisez tous les processeurs? Si c'est le cas, tous les événements répondront plus lentement en raison de la façon dont Windows traite le message. – Jeremy

+1

Je soupçonne que le thread exécutant les threads parallèles (qui est probablement le même thread exécutant le timer) se bloque lui-même. Si vous pouvez trouver un moyen d'exécuter votre minuterie sur un thread différent, votre problème peut disparaître. Le thread qui exécute votre minuteur peut être différent en fonction de la manière dont votre application est hébergée (par exemple Windows Forms vs console) –

Répondre

1

La classe Parallel utilise une installation TPL interne appelée tâches auto répliquant. Ils sont destinés à consommer toutes les ressources de thread disponibles. Je ne sais pas quel genre de limites sont en place, mais il semble que c'est épuisant. J'ai répondu fondamentalement à la même question il y a quelques jours.

La classe Parallel est sujette à engendrer des quantités folles de tâches sans aucune limite. Il est facile de le provoquer pour engendrer littéralement des fils illimités (2 par seconde). Je considère la classe Parallel inutilisable sans un max-DOP spécifié manuellement. C'est une bombe à retardement qui explose en production au hasard sous charge.

Parallel est particulièrement toxique dans les scénarios ASP.NET où de nombreuses demandes partagent un fil piscine.

Mise à jour: J'ai oublié de faire le point clé. Les graduations minuterie sont mises en file d'attente dans le pool de threads. Si le pool est saturé, ils se mettent en ligne et s'exécutent plus tard. (C'est la raison pour laquelle les ticks du timer peuvent se produire simultanément ou après l'arrêt des timers.) Ceci explique ce que vous voyez. La solution consiste à corriger la surcharge de la piscine. La meilleure solution pour ce scénario particulier serait un planificateur de tâches personnalisé avec un nombre fixe de threads. Parallel peut être utilisé pour utiliser ce planificateur de tâches. Les extras d'extension parallèle ont un tel planificateur. Obtenez ce travail hors du pool de threads globalement partagé. Normalement, je recommanderais PLINQ mais ce n'est pas capable de prendre un planificateur. Dans un sens, Parallel et PLINQ sont des API inutilement paralysées.

Ne pas utiliser ThreadPool.SetMinThreads. Ne plaisante pas avec les paramètres globaux du processus. Il suffit de laisser le pauvre bassin de threads seul.

De même, n'utilisez pas Environment.ProcessorCount -1 car cela gaspille un seul cœur.

la minuterie est déjà exécutée dans son propre thread

La minuterie est une structure de données dans le noyau du système d'exploitation. Il n'y a pas de thread jusqu'à ce qu'une coche doit être mise en file d'attente. Je ne sais pas exactement comment cela fonctionne, mais la coche est mise en file d'attente dans le pool de threads dans .NET éventuellement. C'est lorsque le problème commence.

En tant que solution, pourrait démarrer un thread qui dort dans une boucle afin de simuler une minuterie. C'est un hack, cependant, car il ne corrige pas la cause première: Le pool de threads surchargé.