Параллельные функции в .Net 4.0 - PullRequest
12 голосов
/ 05 мая 2010

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

Ответы [ 5 ]

12 голосов
/ 05 мая 2010

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

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

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

6 голосов
/ 05 мая 2010

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

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

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

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

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

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

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

2 голосов
/ 05 мая 2010

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

0 голосов
/ 05 мая 2010

Это очень хороший вопрос, и ответ не на 100% ясен / лаконичен. Я хотел бы обратить ваше внимание на эту ссылку от Micrsoft, в которой подробно изложено КОГДА вы должны использовать параллельные элементы .

0 голосов
/ 05 мая 2010

в ответах и ​​комментариях есть отличное обсуждение: Parallel.For (): обновить переменную вне цикла .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...