Я пробегался через практичность некоторых новых параллельных функций в.Net 4.0.
Скажите, что у меня есть код как так:
foreach (var item in myEnumerable)
myDatabase.Insert(item.ConvertToDatabase());
Вообразите myDatabase. Вставка выполняет некоторую работу для вставки в базу данных SQL.
Теоретически Вы могли записать:
Parallel.ForEach(myEnumerable, item => myDatabase.Insert(item.ConvertToDatabase()));
И автоматически Вы получаете код, который использует в своих интересах несколько ядер.
Но что, если myEnumerable может только взаимодействоваться с единственным потоком? Параллельный класс перечислит единственным потоком и только отправит результат рабочим потокам в цикле?
Что, если myDatabase может только взаимодействоваться с единственным потоком? Конечно, не было бы лучше сделать соединение с базой данных на повторение цикла.
Наконец, что, если мой "объект var", оказывается, UserControl или что-то, что должно взаимодействоваться с на потоке UI?
За каким шаблоном разработки я должен следовать для решения этих проблем?
Это смотрит на меня, что переключение к Parallel/PLinq/etc не точно легко, когда Вы имеете дело с реальными приложениями.
Интерфейс IEnumerable
по своей сути не является потокобезопасным. Parallel.ForEach
будет автоматически обрабатывать это и распараллеливать только элементы, выходящие из вашего перечисления. (Последовательность всегда будет проходить, по одному элементу за раз, по порядку, но результирующие объекты распараллеливаются.)
Если ваши классы (то есть: T) не могут обрабатываться несколькими потоками, вам не следует пытаться распараллелить эту процедуру. Не каждая последовательность является кандидатом на распараллеливание - это одна из причин, почему это не выполняется автоматически компилятором;)
Если вы выполняете работу, требующую работы с потоком пользовательского интерфейса, это все еще потенциально возможно. Однако вам необходимо проявлять такую же осторожность, как и в любое время, когда вы имеете дело с элементами пользовательского интерфейса в фоновых потоках, и маршалировать данные обратно в поток пользовательского интерфейса. Во многих случаях это можно упростить с помощью нового API TaskScheduler.FromCurrentSynchronizationContext
. Я писал об этом сценарии в своем блоге здесь .
Это очень хороший вопрос, и ответ на него не является на 100% ясным/четким. Я бы указал вам на эту ссылку от Micrsoft, в ней изложено много деталей относительно КОГДА следует использовать параллельные элементы.
Как вы уже догадались, использование преимуществ Parallel.For
или Parallel.ForEach
требует от вас способности компоновать вашу работу в дискретные единицы (воплощенные вашим лямбда-выражением, которое передается в Parallel.ForEach
), которые могут выполняться независимо.
Все это законные проблемы, и PLINQ / TPL не пытается их решать. Ваша задача как разработчика - написать код, который может правильно работать при распараллеливании.Нет никакого волшебства, которое компилятор / TPL / PLINQ может сделать для преобразования кода, небезопасного для многопоточности, в потокобезопасный код ... вы должны убедиться, что вы это делаете.
Для некоторых из описанных вами ситуаций вы должны сначала решить, целесообразно ли распараллеливание. Если узким местом будет подключение к базе данных или обеспечение правильной последовательности операций, то, возможно, многопоточность не подходит.
В случае, когда TPL передает перечислимый поток нескольким потокам, ваше предположение верно. Последовательность перечисляется в одном потоке, и каждый рабочий элемент затем (потенциально) отправляется в отдельный поток для выполнения действий. Интерфейс IEnumerable
по своей сути не потокобезопасен, но TPL скрывает это за вас.
Что действительно помогает PLINQ / TPL, так это управлять тем, когда и как распределять работу по нескольким потокам. TPL определяет наличие нескольких ядер на машине и автоматически масштабирует количество потоков, используемых для обработки данных. Если машина имеет только один ЦП / Ядро, то TPL может выбрать , чтобы не распараллеливать работу. Преимущество для вас, разработчика, заключается в том, что вам не нужно писать два разных пути - один для параллельной логики, другой для последовательной. Однако ответственность по-прежнему лежит на вас, чтобы обеспечить безопасный доступ к вашему коду из нескольких потоков одновременно.
Какому шаблону проектирования мне следует следовать, чтобы решить эти проблемы?
На этот вопрос нет однозначного ответа ...однако общая практика заключается в использовании неизменяемости в дизайне вашего объекта. Неизменяемость делает более безопасным использование объекта в нескольких потоках и является одним из наиболее распространенных способов сделать операции доступными для обсуждения. Фактически, такие языки, как F #, широко используют неизменяемость, чтобы язык упростил параллельное программирование.
Если вы используете .NET 4.0, вам также следует изучить классы коллекций ConcurrentXXX
в System.Collections.Concurrent
. Здесь вы найдете несколько безблокировочных и мелкозернистых конструкций коллекции блокировок, которые упрощают написание многопоточного кода.
здесь много обсуждений в ответах и комментариях: Parallel.For (): Обновить переменную вне цикла .
Ответ нет : параллельные расширения не будут думать за вас. Здесь по-прежнему актуальны проблемы с многопоточностью. Это приятный синтаксический сахар, но не панацея.