2008-09-19 11 views
13

Wyobraź sobie, że mam funkcję, która przechodzi przez milion/miliard ciągów i sprawdza się w nich.Przyspieszenie pętli za pomocą wielowątkowość w C# (Pytanie)

f.ex:

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

zużywa partii czasu, ponieważ CalculateSmth jest bardzo czasochłonne funkcji czasu.

Chcę zapytać: jak zintegrować wielowątkowość w tym rodzaju procesu?

f.ex: Chcę odpalić 5 wątków, a każdy z nich zwraca niektóre wyniki, a to się zaczyna, dopóki lista nie zawiera elementów.

Może ktoś może pokazać kilka przykładów i artykułów ..

Zapomniałem wspomnieć muszę go w .NET 2.0

+0

Czy potrzebujesz wyników z powrotem w tej samej kolejności? – Keith

+0

Czy możesz użyć wielu pracowników w tle? utworzyć jakąś logikę, która zajmie liczbę list ciągów, a następnie utworzyć X ilość BW i divy każdy – Crash893

Odpowiedz

17

Można spróbować Parallel extensions (część .NET 4.0)

To pozwoli można napisać coś takiego:

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

oczywiście result.add musiałyby być bezpieczeństwo wątków.

+0

w takim przypadku, czy będzie jakiś warunek wyścigu w zbiorze wyników? przecież wiele wątków może jednocześnie wykonywać result.add ... – cruizer

+0

result.add musi być bezpieczne dla wątków yeah .. – Tobi

+0

Zapomniałem wspomnieć, że potrzebuję tego w .NET 2.0 –

1

Nie mam teraz żadnych dobrych artykułów tutaj, ale to, co chcesz zrobić, to coś z Producenta-Konsumenta z Threadpool.

Producenci przechwytują i tworzą zadania (które w tym przypadku mogą polegać tylko na umieszczeniu w kolejce pozycji na liście lub stosie). Konsumenci to, powiedzmy, pięć wątków, które czytają jeden przedmiot ze stosu, konsumują go przez obliczenie, a następnie zapisują go w innym miejscu.

W ten sposób wielowątkowość jest ograniczona do tych pięciu wątków i wszystkie będą miały pracę do zrobienia, aż stos będzie pusty.

Co o tym myśleć:

  • ochrony umieścić na liście wejściowego i wyjściowego, takiego jak mutex.
  • Jeśli zamówienie jest ważne, upewnij się, że zamówienie wyjściowe jest zachowane. Jednym z przykładów może być przechowywanie ich w SortedList lub coś w tym stylu.
  • Upewnij się, że funkcja CalculateSmth jest bezpieczna dla wątków, że nie używa żadnego stanu globalnego.
2

Na pierwsze pytanie należy odpowiedzieć, czy należy używać gwintowania

Jeśli CalculateSmth function() jest w zasadzie CPU-bound, czyli ciężki w CPU-usage i zasadniczo nie I/O-usage, wtedy ciężko jest mi dostrzec sens używania wątków, ponieważ wątki będą konkurować o ten sam zasób, w tym przypadku procesor.

Jeśli twoja funkcja CalculateSmth() używa zarówno procesora, jak i wejścia/wyjścia, może to być punkt korzystania z wątków.

Całkowicie zgadzam się z komentarzem do mojej odpowiedzi. Zrobiłem błędne założenie, że mówimy o pojedynczym procesorze z jednym rdzeniem, ale obecnie mamy wielordzeniowe procesory, moje złe.

+1

Zależy, czy jest to system wielordzeniowy. Jeśli masz na przykład cztery rdzenie, użycie czterech wątków powinno spowodować przybliżone czterokrotne przyspieszenie przetwarzania (zakładając brak współzależności między wątkami). –

18

Rozszerzenia równoległy jest fajne, ale może to być również wykonane tylko za pomocą puli wątków tak:

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

A skąd wiadomo, kiedy to się skończy? mmm. – leppie

+0

Może mieć ManualResetEvent wywoływany przez funkcję WaitCallback i główny wątek WaitOne on. –

+0

Dodano kod, aby pokazać, w jaki sposób można użyć MRE, aby to zrobić. – noocyte

12

Zauważ, że współbieżności nie magicznie daje więcej zasobów. Musisz ustalić, co spowalnia obliczenie CalculateSmth.

Na przykład, jeśli jest związany z procesorem (i jesteś na jednym rdzeniu), to ta sama liczba tyknięć procesora trafi do kodu, niezależnie od tego, czy wykonasz je sekwencyjnie, czy równolegle. Plus będziesz mieć pewne koszty związane z zarządzaniem wątkami. Ten sam argument odnosi się do innych ograniczeń (np. I/O):

Otrzymasz wzrost wydajności tylko wtedy, gdy CalculateSmth pozostawi wolne zasoby podczas jego wykonywania, które może być użyte przez inną instancję. To nie jest rzadkie. Na przykład, jeśli zadanie obejmuje IO, po którym następuje trochę CPU, proces 1 może wykonywać procesor, podczas gdy proces 2 wykonuje IO. Jak zauważają maty, łańcuch producentów-konsumentów może to osiągnąć, jeśli masz infrastrukturę.

5

Musisz dzielić pracę, którą chcesz wykonać równolegle. Oto przykład, jak podzielić dzieło na dwie części:

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;