Лучшая реализация Threading - PullRequest
       4

Лучшая реализация Threading

0 голосов
/ 19 февраля 2012

У меня есть приложение, в котором у меня одновременно запущено несколько «поисков» (поиск занимает 1–10 секунд, в зависимости от того, сколько результатов доступно). Проблема в том, что задержка при поиске продолжает увеличиваться(Я думаю, потому что 25 Макс потоков) Я использую Backgroundworker Class Atm.Итак, я посмотрел несколько других реализаций:

Простые примеры:

    static void Main()
{

    for (int i = 0; i < 500; i++)
    {
        try
        {
            new Thread(new ParameterizedThreadStart(doWork)).Start(i);
        }
        catch { }
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
    Thread.CurrentThread.Abort();
}

Но я получаю исключения, которые прерывают потоки (что меня беспокоит) Поэтому я попытался с пулом потоков:

static void Main()
{

    for (int i = 0; i < 500; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(doWork), i);
    }
    Console.ReadLine();
}
static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}

Но все идет очень медленно ...

Я все еще ищу лучшую реализацию, кто-нибудь может мне помочь?

РЕДАКТИРОВАТЬ: Метод DoWorkСоздает сетевое соединение (и ждет его завершения) Это с помощью API, поэтому я не могу выполнить async

Ответы [ 6 ]

1 голос
/ 19 февраля 2012

Все, что включает в себя очередь из 500 элементов для обработки ThreadPool, не будет работать с оптимальной пропускной способностью. ThreadPool, как правило, довольно неохотно раскручивает дополнительные потоки, поскольку такое использование не предназначено для дизайнеров.

Мне кажется, что вы привязаны к IO, и в этом случае вы можете выполнять IO асинхронно и обслуживать все, используя очень мало потоков. Однако, не зная больше о вашей рабочей нагрузке, это всего лишь игра в догадки.

1 голос
/ 19 февраля 2012

Попробуйте это решение:

static void Main(string[] args)
{
    for (int i = 0; i < 100; i++)
    {
        Task t = new Task(doWork, i);
        t.Start();
    }
    Console.ReadLine();
}

static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.SpinWait(20000000); // It depends on what doWork actually does whether SpinWait or Sleep is the most appropriate test
    //Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}

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

Итак, чтобы ответить на вопрос, нам нужно знать, что на самом деле делает doWork :-) Но в целом Task будет хорошим выбором и хорошей абстракцией.

Параллельно foreach

Если вы используете цикл для порождения заданий и выполняете параллелизм данных , тогда параллельный foreach может выполнить эту работу:

Parallel.For(0, 500, i => doWork(i));

Ссылки:

На комментарий от спонсора

http://msdn.microsoft.com/en-us/library/dd537609.aspx

Задачи предоставляют два основных преимущества:

1) Более эффективное и масштабируемое использование системных ресурсов.

За кулисами задачи ставятся в очередь в ThreadPool, который был усилены алгоритмами (такими как восхождение на холм), которые определяют и настроить количество потоков, которое максимизирует пропускную способность. Это делает Задачи относительно легки, и вы можете создать много из них, чтобы включить мелкозернистый параллелизм. В дополнение к этому, широко известный алгоритмы кражи работ используются для обеспечения балансировки нагрузки.

2) Больше программного управления, чем возможно для потока или рабочего элемента.

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

Обновленный ответ

Ну, к сожалению, это плохой API, потому что он не позволяет делать это асинхронно. Вероятно, он идет медленно, потому что вы одновременно запускаете много соединений (или слишком мало).

Попробуйте это:

var jobs = new[] { 1, 2, 3};
var options = new ParallelOptions { MaxDegreeOfParallelism = 3 };
Parallel.ForEach(jobs, options, i => doWork(i));

И поэкспериментируйте со значением MaxDegreeOfParallelism.

1 голос
/ 19 февраля 2012

Если поиск связан с ЦП, я бы использовал Parallel.For или параллельный linq с указанным вручную MaxDegreeOfParallelism. Обычно количество виртуальных ядер является оптимальным числом потоков в этом случае.

Если поиск ожидает что-то внешнее (например, связанный с вводом-выводом, ожидает ответов по сети ...), я бы посмотрел на неблокирующие API, поэтому вам не нужен поток для поиска.

0 голосов
/ 19 февраля 2012

Пул потоков пытается минимизировать количество созданных потоков, и вместо создания новых потоков для поставленных в очередь задач он может ожидать освобождения других потоков в пуле.Это происходит по причине - слишком много потоков могут снизить производительность.Но вы можете переопределить минимальное количество потоков, которое будет создано до того, как произойдет регулирование.

Вот ваш оригинальный код с исправлением, чтобы он работал быстро:

static void Main()
{
    int minWorker, minIOC;
    ThreadPool.GetMinThreads(out minWorker, out minIOC);
    ThreadPool.SetMinThreads(50, minIOC);
    for (int i = 0; i < 500; i++)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(doWork), i);
    }
    Console.ReadLine();
}

static void doWork(object i)
{
    Console.WriteLine(i + ": started");
    Thread.Sleep(1000);
    Console.WriteLine(i + " done");
}
0 голосов
/ 19 февраля 2012

1) Если вы вызываете прерывание, вы получаете исключение ThreadAbortException: Thread.Abort

2) ¿500 тем? Я думаю, что вы делаете что-то плохим образом (если вы не работаете с графическими процессорами)

0 голосов
/ 19 февраля 2012

Не используйте Thread.Abort. Это довольно жестокий конец плохой рабочей угрозе, которая просто хотела изящно покончить. Вы можете просто дать исходному методу doWork завершиться, и поток будет освобожден.

А что касается вашего второго решения - вы помещаете в очередь 500 потоков в секунду, что намного больше, чем ThreadPool может одновременно запускать. Это занимает больше времени, потому что они бегут один за другим.

Другой вариант в .NET 4.0 - это библиотека задач в System.Threading.Tasks, которая является более разумным решением на основе потокового пула, которое вы могли бы рассмотреть.

Но вернемся к исходной проблеме - что именно вы подразумеваете под "задержкой, которая увеличивается" при использовании BackgroundWorkers? Вы имеете в виду, что каждый отдельный поиск занимает больше времени (ближе к 10 секундам), когда происходит многократный поиск? Если так, я бы попытался найти узкое место в другом месте. Что такое «поиск»? Вы обращаетесь к базе данных? Сетевое подключение? Возможно, у вас есть блокировки в той части приложения, которые вызывают узкие места.

...