Ускорение цикла с использованием многопоточности в C # (Вопрос) - PullRequest
13 голосов
/ 19 сентября 2008

Представьте, что у меня есть функция, которая просматривает миллион / миллиард строк и проверяет что-то в них.

f.ex:

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

это отнимает много времени, потому что CalculateSmth - очень трудоемкая функция.

Я хочу спросить: как интегрировать многопоточность в этот своего рода процесс?

f.ex: я хочу запустить 5 потоков, каждый из которых возвращает некоторые результаты, и это продолжается до тех пор, пока в списке нет элементов.

Может быть, кто-нибудь может показать несколько примеров или статей ..

Забыл упомянуть, что мне это нужно в .NET 2.0

Ответы [ 6 ]

18 голосов
/ 19 сентября 2008

Параллельные расширения - это круто, но это также можно сделать, просто используя пул потоков следующим образом:

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);
        }
    }
}
17 голосов
/ 19 сентября 2008

Вы можете попробовать Параллельные расширения (часть .NET 4.0)

Они позволяют вам написать что-то вроде:

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

Конечно, result.add должен быть потокобезопасным.

12 голосов
/ 19 сентября 2008

Обратите внимание, что параллелизм магическим образом не дает вам больше ресурсов. Вам нужно установить, что замедляет CalculateSmth down.

Например, если он привязан к процессору (и вы используете одно ядро), то одинаковое количество тактов процессора попадет в код независимо от того, выполняете ли вы их последовательно или параллельно. Кроме того, вы получите некоторые накладные расходы от управления потоками. Тот же аргумент применяется к другим ограничениям (например, ввод / вывод)

Вы получите прирост производительности только в том случае, если CalculateSmth оставляет ресурс свободным во время его выполнения, который может быть использован другим экземпляром. Это не редкость. Например, если задача включает в себя ввод-вывод, за которым следуют некоторые операции с процессором, то процесс 1 может выполнять работу с процессором, а процесс 2 выполняет ввод-вывод. Как указывает Мэтс, цепочка производителей-потребителей может достичь этого, если у вас есть инфраструктура.

5 голосов
/ 19 сентября 2008

Вам нужно разделить работу, которую вы хотите делать параллельно. Вот пример того, как вы можете разделить работу на две части:

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;
2 голосов
/ 19 сентября 2008

Первый вопрос, на который вы должны ответить, следует ли использовать потоки

Если ваша функция CalculateSmth () в основном привязана к процессору, т. Е. Интенсивно использует процессор и практически не использует ввод-вывод, тогда мне трудно понять смысл использования потоков, поскольку потоки будут конкурировать за тот же ресурс, в данном случае процессор.

Если ваш CalculateSmth () использует как процессор, так и ввод / вывод, то это может быть точка в использовании потоков.

Я полностью согласен с комментарием к моему ответу. Я сделал ошибочное предположение, что мы говорим об одном процессоре с одним ядром, но в наши дни у нас многоядерные процессоры, мой плохой.

1 голос
/ 19 сентября 2008

Не то чтобы у меня сейчас были какие-то хорошие статьи, но то, что вы хотите сделать, это что-то вроде Producer-Consumer с Threadpool.

Producers выполняет циклы и создает задачи (которые в данном случае могли бы просто поставить в очередь элементы списка или стека). Потребителями являются, скажем, пять потоков, которые считывают один элемент из стека, потребляют его, вычисляя, а затем сохраняют в другом месте.

Таким образом, многопоточность ограничена только этими пятью потоками, и им всем придется работать до тех пор, пока стек не опустеет.

Что нужно подумать:

  • Поместить защиту в список ввода и вывода, например, мьютекс.
  • Если порядок важен, убедитесь, что порядок вывода поддерживается. Одним из примеров может быть их хранение в SortedList или что-то в этом роде.
  • Убедитесь, что CalculateSmth является потокобезопасным, что он не использует глобальное состояние.
...