Parallel.ForEach не раскручивает новые темы - PullRequest
2 голосов
/ 07 декабря 2009

Parallel.ForEach не раскручивает новые темы

Привет всем, у нас очень интенсивная операция ввода-вывода, которую мы написали с использованием Parallel.ForEach из Microsoft Parallel Extensions для .NET Framework. Нам нужно удалить большое количество файлов, и мы представляем файлы для удаления в виде списка списков. Каждый вложенный список содержит 1000 сообщений, и у нас есть 50 из этих списков. Проблема здесь в том, что когда я просматриваю журналы впоследствии, я вижу только один поток, выполняющийся внутри нашего блока Parallel.ForEach.

Вот как выглядит код:

List<List<Message>> expiredMessagesLists = GetNestedListOfMessages();
foreach (List<Message> subList in expiredMessagesLists)
{
    Parallel.ForEach(subList, msg =>
    {
        try
        {
            Logger.LogEvent(TraceEventType.Information, "Purging Message {0} on Thread {1}", msg.MessageID, msg.ExtensionID, Thread.CurrentThread.Name);

            DeleteMessageFiles(msg);
        }
        catch (Exception ex)
        {
            Logger.LogException(TraceEventType.Error, ex);
        }
    });
}

Я написал пример кода с более простой структурой данных и без логики ввода-вывода, и я мог видеть несколько различных потоков, выполняющихся в блоке Parallel.ForEach. Мы делаем что-то неправильно с Parallel.ForEach в приведенном выше коде? Это может быть список списков, который его отключает, или есть какое-то ограничение потоков для операций ввода-вывода?

Ответы [ 2 ]

6 голосов
/ 07 декабря 2009

Есть несколько возможностей.

Во-первых, в большинстве случаев Parallel.ForEach не создаст новую тему. Он использует .NET 4 ThreadPool (все TPL поддерживает) и будет повторно использовать потоки ThreadPool.

При этом Parallel.ForEach использует стратегию секционирования, основанную на размере передаваемого ему списка. Мое первое предположение состоит в том, что ваш «внешний» список содержит много сообщений, но внутренний список имеет только один экземпляр Message, поэтому разделитель ForEach использует только один поток. С одним элементом Parallel достаточно умен, чтобы просто использовать основной поток, а не крутить работу в фоновом потоке.

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

Еще одна возможность:

Попробуйте использовать [Thread.ManagedThreadId][1] вместо Thread.CurrentThread.Name для ведения журнала. Поскольку Parallel использует потоки ThreadPool, «Имя» часто одинаково для нескольких потоков. Вы можете подумать, что используете только один поток, хотя на самом деле вы используете более одного ...

1 голос
/ 07 декабря 2009

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

Предположим, у вас есть класс Person с методом, называемым RaiseArm(). Вы всегда можете попытаться отстрелить RaiseArm() на 100 различных потоках, но Person сможет поднять только два за раз ...

Как я уже сказал, я могу ошибаться. Это всего лишь мое подозрение.

...