2008-09-19 17 views
13

Immaginate di avere una funzione che attraversa un milione/miliardo di stringhe e controlla in modo smth.Ciclo di accelerazione utilizzando il multithreading in C# (Domanda)

f.ex:

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

consuma una sacco di tempo, perché CalculateSmth è molto tempo la funzione che consumano.

Voglio chiedere: come integrare il multithreading in questo tipo di processo?

f.ex: Voglio accendere 5 thread e ognuno di loro restituisce alcuni risultati, e questo va avanti fino a quando la lista non ha elementi.

Forse qualcuno può mostrare alcuni esempi o articoli ..

dimenticato di dire che ho bisogno in .NET 2.0

+0

Sono necessari i risultati nello stesso ordine? – Keith

+0

Potresti usare più operatori in background? creare una sorta di logica che prenderebbe il conteggio dell'elenco di stringhe quindi creerà X quantità di BW e dividerà ciascuna – Crash893

risposta

17

Si potrebbe provare il Parallel extensions (parte di .NET 4.0)

Questi permettono di scrivere qualcosa di simile a:

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

Naturalmente result.add avrebbe bisogno di essere thread-safe.

+0

in questo caso, ci sarà qualche condizione di competizione nella raccolta dei risultati? dopo che tutti i thread multipli potrebbero essere in esecuzione result.add simultaneamente ... – cruizer

+0

result.add deve essere thread safe sì .. – Tobi

+0

Ho dimenticato di menzionarlo ne ho bisogno in .NET 2.0 –

1

Non è che io abbia qualche buon articolo qui adesso, ma quello che vuoi fare è qualcosa lungo Producer-Consumer con un Threadpool.

I produttori eseguono il loop e creano attività (che in questo caso potrebbero essere solo accodare gli elementi in un elenco o stack). I consumatori sono, ad esempio, cinque thread che rilevano un elemento fuori dallo stack, lo consuma calcolandolo e quindi lo memorizza altrove.

In questo modo il multithreading è limitato a solo cinque thread e tutti avranno del lavoro da fare fino a quando lo stack non sarà vuoto.

Cose a cui pensare:

  • protezione messo in lista di ingresso e uscita, come ad esempio un mutex.
  • Se l'ordine è importante, assicurarsi che l'ordine di uscita sia mantenuto. Un esempio potrebbe essere quello di memorizzarli in una SortedList o qualcosa del genere.
  • Assicurarsi che il CalculateSmth sia thread-safe, che non usi nessuno stato globale.
2

La prima domanda si deve rispondere è se si dovrebbe utilizzare la filettatura

Se la vostra funzione CalculateSmth() è fondamentalmente CPU-bound, cioè pesante in CPU-utilizzo e praticamente nessun I/O-utilizzo, quindi ho difficoltà a vedere il punto di utilizzo dei thread, dal momento che i thread competeranno sulla stessa risorsa, in questo caso la CPU.

Se il tuo CalculateSmth() utilizza sia CPU che I/O, potrebbe essere utile utilizzare il threading.

Sono totalmente d'accordo con il commento alla mia risposta. Ho assunto erroneamente che stavamo parlando di una singola CPU con un core, ma oggigiorno abbiamo CPU multi-core, il mio male.

+1

Dipende se si tratta di un sistema multi-core. Se si dispone di quattro core disponibili, ad esempio, l'utilizzo di quattro thread dovrebbe vedere un aumento di velocità approssimativo di quattro volte nell'elaborazione (presupponendo che non vi siano interdipendenze tra i thread). –

18

Le estensioni parallelo è fresco, ma questo può anche essere fatto semplicemente utilizzando il ThreadPool come questo:

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

E come fai a sapere quando è finito? mmm. – leppie

+0

Potrebbe avere un ManualResetEvent che la funzione WaitCallback chiama e il thread principale WaitOne attivo. –

+0

Aggiunto codice per mostrare come è possibile utilizzare MRE per farlo. – noocyte

12

Si noti che la concorrenza non magicamente vi darà più risorse. È necessario stabilire cosa sta rallentando CalculateSmth.

Ad esempio, se è collegato alla CPU (e si è su un singolo core), lo stesso numero di tick della CPU andrà al codice, indipendentemente dal fatto che vengano eseguiti in sequenza o in parallelo. Inoltre si otterrebbe un po 'di overhead dalla gestione dei thread. Lo stesso argomento vale per altri vincoli (ad es. I/O)

In questo caso si ottengono guadagni in termini di prestazioni solo se CalculateSmth non rilascia risorse durante l'esecuzione, che potrebbero essere utilizzate da un'altra istanza. Questo non è raro. Ad esempio, se l'attività coinvolge l'I/O seguito da alcune risorse della CPU, il processo 1 potrebbe occuparsi della CPU mentre il processo 2 sta eseguendo l'IO. Come indicato dalle stuoie, una catena di unità produttore-consumatore può raggiungere questo obiettivo, se si dispone dell'infrastruttura.

5

È necessario suddividere il lavoro che si desidera eseguire in parallelo. Ecco un esempio di come è possibile suddividere il lavoro in due:

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;