2008-09-19 12 views
13

Imaginez que j'ai une fonction qui passe par un million/milliard de chaînes et qui vérifie en elles.Accélérer la boucle en utilisant le multithreading en C# (Question)

p.ex:

foreach (String item in ListOfStrings) 
{ 
    result.add(CalculateSmth(item)); 
} 

il consomme beaucoup de temps, parce que CalculateSmth prend beaucoup de temps la fonction.

Je voudrais demander: comment intégrer le multithreading dans ce processus? F.ex: Je veux allumer 5 threads et chacun d'entre eux retourne des résultats, et ça continue jusqu'à ce que la liste ait des éléments.

Peut-être que tout le monde peut montrer des exemples ou des articles ..

oublié de mentionner que j'ai besoin dans .NET 2.0

+0

Avez-vous besoin des résultats dans le même ordre? – Keith

+0

Pourriez-vous utiliser plusieurs travailleurs d'arrière-plan? créer une sorte de logique qui prendrait le compte de la liste des chaînes puis créer X quantité de BW et diviser chacun – Crash893

Répondre

17

Vous pouvez essayer la Parallel extensions (partie 4.0 de .NET)

Ceux-ci permettent vous écrire quelque chose comme:

Parallel.Foreach (ListOfStrings, (item) => 
    result.add(CalculateSmth(item)); 
); 

Bien sûr, result.add doit être thread-safe.

+0

dans ce cas, y aura-t-il des conditions de concurrence dans la collection de résultats? après tout, plusieurs threads peuvent être en train d'exécuter result.add en même temps ... – cruizer

+0

result.add doit être thread safe ouais .. – Tobi

+0

Oublié de mentionner que j'en ai besoin. NET 2.0 –

1

Je n'ai pas de bons articles ici en ce moment, mais ce que vous voulez faire est quelque chose le long de Producer-Consumer avec un Threadpool.

Le producteur boucle et crée des tâches (ce qui dans ce cas pourrait être de simplement mettre en file d'attente les éléments d'une liste ou d'une pile). Les consommateurs sont, disons, cinq threads qui lisent un élément de la pile, le consomment en le calculant, puis le stockent ailleurs. De cette façon, le multithreading est limité à ces cinq threads, et ils auront tous du travail à faire jusqu'à ce que la pile soit vide.

choses à penser:

  • protection Put sur l'entrée et la liste de sortie, comme un mutex.
  • Si la commande est importante, assurez-vous que l'ordre de sortie est maintenu. Un exemple pourrait être de les stocker dans un SortedList ou quelque chose comme ça.
  • Assurez-vous que CalculateSmth est thread-safe, qu'il n'utilise aucun état global.
2

La première question que vous devez répondre est de savoir si vous devez utiliser le filetage

Si votre CalculateSmth de fonction() est essentiellement liée CPU, à savoir lourd dans CPU-usage et pratiquement pas d'E/S-utilisation, alors j'ai du mal à voir le point d'utiliser les threads, puisque les threads seront en compétition sur la même ressource, dans ce cas le CPU.

Si votre CalculateSmth() utilise à la fois le processeur et les E/S, l'utilisation du thread peut être utile. Je suis totalement d'accord avec le commentaire de ma réponse.

J'ai fait une supposition erronée que nous parlions d'un seul processeur avec un noyau, mais ces jours-ci nous avons des processeurs multi-core, mon mauvais.

+1

Cela dépend si c'est un système multi-core. Si vous avez quatre cœurs disponibles, par exemple, l'utilisation de quatre threads devrait entraîner une accélération d'environ quatre fois dans le traitement (en supposant qu'il n'y ait pas d'interdépendance entre les threads). –

18

Les extensions parallèles est cool, mais cela peut aussi se faire en utilisant simplement la threadpool comme ceci:

using System.Collections.Generic; 
using System.Threading; 

namespace noocyte.Threading 
{ 
    class CalcState 
    { 
     public CalcState(ManualResetEvent reset, string input) { 
      Reset = reset; 
      Input = input; 
     } 
     public ManualResetEvent Reset { get; private set; } 
     public string Input { get; set; } 
    } 

    class CalculateMT 
    { 
     List<string> result = new List<string>(); 
     List<ManualResetEvent> events = new List<ManualResetEvent>(); 

     private void Calc() { 
      List<string> aList = new List<string>(); 
      aList.Add("test"); 

      foreach (var item in aList) 
      { 
       CalcState cs = new CalcState(new ManualResetEvent(false), item); 
       events.Add(cs.Reset); 
       ThreadPool.QueueUserWorkItem(new WaitCallback(Calculate), cs); 
      } 
      WaitHandle.WaitAll(events.ToArray()); 
     } 

     private void Calculate(object s) 
     { 
      CalcState cs = s as CalcState; 
      cs.Reset.Set(); 
      result.Add(cs.Input); 
     } 
    } 
} 
+1

Et comment savez-vous quand c'est fini? mmm. – leppie

+0

Pourrait avoir un ManualResetEvent que la fonction WaitCallback appelle et le thread principal WaitOne. –

+0

Ajout d'un code pour montrer comment vous pouvez utiliser MRE pour cela. – noocyte

12

Notez que ne vous donne accès simultané pas comme par magie plus de ressources. Vous devez établir ce qui ralentit CalculateSmth vers le bas. Par exemple, s'il est lié au processeur (et que vous êtes sur un seul cœur), le même nombre de cœurs de processeur ira au code, que vous les exécutiez séquentiellement ou en parallèle. De plus, vous aurez des frais généraux de gestion des threads. Le même argument s'applique à d'autres contraintes (par exemple E/S)

Vous obtiendrez uniquement des gains de performance si CalculateSmth laisse une ressource libre pendant son exécution, qui pourrait être utilisée par une autre instance. Ce n'est pas rare. Par exemple, si la tâche implique des E/S suivies de certains éléments de l'UC, alors le processus 1 pourrait faire les choses du CPU tandis que le processus 2 ferait l'E/S. Comme le souligne le programme, une chaîne d'unités producteur-consommateur peut y parvenir, si vous avez l'infrastructure.

5

Vous devez diviser le travail que vous voulez faire en parallèle. Voici un exemple de comment diviser le travail en deux:

List<string> work = (some list with lots of strings) 

// Split the work in two 
List<string> odd = new List<string>(); 
List<string> even = new List<string>(); 
for (int i = 0; i < work.Count; i++) 
{ 
    if (i % 2 == 0) 
    { 
     even.Add(work[i]); 
    } 
    else 
    { 
     odd.Add(work[i]); 
    } 
} 

// Set up to worker delegates 
List<Foo> oddResult = new List<Foo>(); 
Action oddWork = delegate { foreach (string item in odd) oddResult.Add(CalculateSmth(item)); }; 

List<Foo> evenResult = new List<Foo>(); 
Action evenWork = delegate { foreach (string item in even) evenResult.Add(CalculateSmth(item)); }; 

// Run two delegates asynchronously 
IAsyncResult evenHandle = evenWork.BeginInvoke(null, null); 
IAsyncResult oddHandle = oddWork.BeginInvoke(null, null); 

// Wait for both to finish 
evenWork.EndInvoke(evenHandle); 
oddWork.EndInvoke(oddHandle); 

// Merge the results from the two jobs 
List<Foo> allResults = new List<Foo>(); 
allResults.AddRange(oddResult); 
allResults.AddRange(evenResult); 

return allResults; 
Questions connexes