Подтвердить что ты не робот

Параллельные функции .Net 4.0

Я рассмотрел практичность некоторых новых параллельных функций в .NET 4.0.

Скажем, у меня такой код:

foreach (var item in myEnumerable)
    myDatabase.Insert(item.ConvertToDatabase());

Представьте, что myDatabase.Insert выполняет некоторую работу для вставки в базу данных SQL.

Теоретически вы можете написать:

Parallel.ForEach(myEnumerable, item => myDatabase.Insert(item.ConvertToDatabase()));

И автоматически вы получаете код, который использует несколько ядер.

Но что, если myEnumerable может взаимодействовать только с одним потоком? Будет ли класс Parallel перечислить одним потоком и только отправить результат рабочим потокам в цикле?

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

Наконец, что, если мой "элемент var" является UserControl или что-то, с чем необходимо взаимодействовать с потоком пользовательского интерфейса?

Какой шаблон проектирования следует использовать для решения этих проблем?

Мне кажется, что переключение на Parallel/PLinq/etc не так просто, когда вы имеете дело с реальными приложениями.

4b9b3361

Ответ 1

Интерфейс IEnumerable<T> по своей сути не является потокобезопасным. Parallel.ForEach будет автоматически обрабатывать это и только распараллелить элементы, выходящие из вашего перечисления. (Последовательность всегда будет проходить, по одному элементу за раз, по порядку - но результирующие объекты будут распараллеливаться.)

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

Если вы выполняете работу, которая требует работы с потоком пользовательского интерфейса, это все еще возможно. Тем не менее, вам нужно будет проявлять такую ​​же осторожность, как и когда бы вы ни сталкивались с элементами пользовательского интерфейса в фоновом потоке, и маршалировать данные обратно на поток пользовательского интерфейса. Во многих случаях это можно упростить, используя новый TaskScheduler.FromCurrentSynchronizationContext API. Я написал о этот сценарий в моем блоге здесь.

Ответ 2

Все это законные проблемы - и PLINQ/TPL не пытаются их устранить. Все еще ваша работа как разработчика для написания кода, который может функционировать правильно при распараллеливании. Там нет волшебства, которое компилятор /TPL/PLINQ может сделать, чтобы преобразовать код, который небезопасен для многопоточности в потокобезопасный код... вы должны убедиться, что это так.

В некоторых ситуациях, которые вы описали, вы должны сначала решить, является ли распараллеливание даже разумным. Если узкое место будет получать соединение с базой данных или обеспечить правильное упорядочение операций, то, возможно, многопоточность isn ' t.

В случае того, как потоки TPL перечислимы для нескольких потоков, ваше предположение верно. Последовательность перечисляется в одном потоке, и каждый рабочий элемент затем (потенциально) отправляется в отдельный поток, на который будет действовать. Интерфейс IEnumerable<T> по своей сути не является потокобезопасным, но TPL обрабатывает это за сцены для вас.

Что PLINQ/TPL помогает вам в этом, управляет тем, как и как отправлять работу на несколько потоков. TPL обнаруживает, когда на машине имеется несколько ядер, и автоматическое масштабирование количества используемых потоков для обработки данных. Если на компьютере имеется только один процессор/ядро, тогда TPL может выбрать не распараллеливать работу. Выгода, разработчик, не должна писать два разных пути: один для параллельной логики, один для последовательного. Тем не менее, ответственность по-прежнему остается за вами, чтобы убедиться, что ваш код может быть безопасно доступен из нескольких потоков одновременно.

Какую схему проектирования следует придерживаться решить эти проблемы?

Нет ответа на этот вопрос... однако, общая практика заключается в использовании неизменяемости в вашем проекте объекта. Неизменность делает его более безопасным для использования объекта в нескольких потоках и является одним из наиболее распространенных методов при выполнении операций. На самом деле, такие языки, как F #, широко используют неизменность, позволяя языку упростить параллельное программирование.

Если вы используете .NET 4.0, вы также должны изучить классы коллекций ConcurrentXXX в System.Collections.Concurrent. Здесь вы найдете некоторые блокирующие и мелкозернистые блокирующие сборные конструкции, которые упрощают запись многопоточного кода.

Ответ 3

Как вы поняли, использование Parallel.For или Parallel.ForEach требует, чтобы у вас была возможность записать вашу работу в дискретные единицы (воплощенные вашим оператором лямбда, переданным в Parallel.ForEach), которые могут быть выполнены независимо друг от друга.