2010-05-05 2 views
12

Я изучаю практичность некоторых новых параллельных функций в .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 не совсем прост, когда вы имеете дело с приложениями реального мира.

ответ

12

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

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

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

+1

Лучший ответ на этот вопрос: скажем, мое тело цикла выполняет длительную операцию ввода-вывода (сетевой запрос, базу данных и т. Д.), Будет ли Parallel-класс обнаруживать спящие/приостановленные потоки и автоматически запускать новый? Или это будет ограничено количеством ядер на машине? – jonathanpeppers

+0

@ Jonathan.Peppers: Планировщик задач по умолчанию справляется с этим довольно хорошо. Это добавит дополнительную работу в ситуацию. (По умолчанию ThreadPool использует гораздо больше элементов, чем потоки, и динамически масштабируется на основе рабочей нагрузки) –

2

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

+0

Выполняют ли какие-либо проблемы в реальном мире эти критерии? Другими словами, сможет ли среднее приложение использовать эти параллельные функции? – jonathanpeppers

+0

@ Джонатан: Абсолютно. Взгляните на эту презентацию Скотта Гензельмана, где он показывает яркий пример того, как это работает. http://channel9.msdn.com/posts/matthijs/Lap-Around-NET-4-with-Scott-Hanselman/ Демонстрация начинается через 38 минут, 55 секунд в разговоре и заканчивается в 47:02. –

+0

По-видимому, у их веб-сайта есть проблемы с пропуском до 38:55, мне придется посмотреть все это дома и вернуться к вам. Я все еще скептически отношусь к тому, что они приведут хороший пример. – jonathanpeppers

0

есть отличное обсуждение в ответах и ​​комментариях здесь: Parallel.For(): Update variable outside of loop.

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

+0

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

6

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

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

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

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

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

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

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

0

Это очень хороший вопрос, и ответ не на 100% ясный/лаконичный. Я хотел бы указать вам на эту ссылку от Micrsoft, она подробно изложена в отношении WHEN you should use the parallel items.