PATTERNS OF PARALLEL PROGRAMMING / ПАТТЕРНЫ ПАРАЛЛЕЛЬНОГО ПРОГРАММИРОВАНИЯ: ПЕРЕДАЧА ДАННЫХ
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
В .NET Framework существует несколько общепринятых способов передачи данных в асинхронную задачу. vzhik 04.07.11 в 07:58 |
|
|
ЗАМЫКАНИЯ vzhik 04.07.11 в 07:58 |
||
|
После того, как замыкания были введены в языки C# и Visual Basic, они стали простейшим способом передачи данных в фоновые операции. При обнаружении делегата, который ссылается на объект вне поля его видимости, компилятор преобразовывает переменные, к которым выполняется доступ, таким образом, чтобы они стали видны делегату, как бы "закрывая сверху" эти переменные. Благодаря этому механизму передача любого объема данных в фоновую задачу не представляет трудностей: vzhik 04.07.11 в 08:06 |
||
|
В приложениях, требующих предельной производительности и масштабируемости, важно помнить что внутри компилятор выделяет объект, в котором сохраняет переменные (в примере выше это data1 и data2), к которым обращается делегат. vzhik 04.07.11 в 10:45 |
||
|
ОБЪЕКТЫ СОСТОЯНИЯ vzhik 04.07.11 в 10:46 |
||
|
Обратившись к истокам .NET Framework, большинство API, порождающих асинхронные задачи, принимает состояние в качетстве параметра и передает это состояние в делегат, который и является телом задачи. ThreadPool.QueueUserWorkItem - главный тому пример: vzhik 04.07.11 в 11:00 |
||
|
Мы можем воспользоваться этим параметром для передачи состояния в WaitCallback: vzhik 04.07.11 в 11:02 |
||
|
Класс Task из .NET Framework 4 также поддерживает этот способ: vzhik 04.07.11 в 11:02 |
||
|
Обратите внимание, что в отличие от замыканий, в этом подходе обычно отсутствует выделение дополнительного объекта для хранения состояния, кроме случаев, когда состояние является типом значения (тип значение будет боксирован при передаче через параметр состояния). vzhik 04.07.11 в 11:07 |
||
|
Чтобы передать несколько порций данных таким способом, они должны быть обернуты в один объект. Раньше это обычно достигалось за счет дополнительного класса, который хранит эту информацию. В .NET Framework 4 можно использовать новый класс Tuple{}: vzhik 04.07.11 в 11:50 |
||
|
И при замыканиях и при работе с типами значений требуется выделение объекта для создания кортежа, оборачивающего составные элементы. Также встроенный в .NET Framework 4 тип кортежа имеет ограничение на хранимое количество единиц. vzhik 04.07.11 в 15:07 |
||
|
ОБЪЕКТЫ СОСТОЯНИЯ С МЕТОДАМИ vzhik 06.07.11 в 13:32 |
||
|
Еще один подход, схожий с предыдущим, заключается в передаче данных в асинхронную операцию в виде метода экземпляра класса, который и представляет асинхронную работу. Это позволяет явно передать данные в метод, используя this. vzhik 06.07.11 в 13:35 |
||
|
Этот способ также требует выделения объекта (экземпляр класса Work) для хранения состояния. Даже если сделать Work структурой этого выделения всё равно не избежать, поскольку создание делегата, ссылающегося на Work, требует хранения ссылки на экземпляр, у которого будет вызван метод Run. Эта ссылка хранится как объект, а значит структура будет боксирована. vzhik 06.07.11 в 13:44 |
||
|
А раз так, выбор конкретного подхода - в основном зависит от личных предпочтений. При использовании замыканий как правило получается более читаемый код, да и компилятор может оптимизировать создание объектов состояния. Например, если анонимная функция передаваемая в StartNew не использует локальное состояние, компилятор может не выделять объект для хранения состояния, поскольку оно уже хранится в виде доступного экземплярного или статического поля. vzhik 06.07.11 в 13:54 |
||
|
АНТИ-ПАТТЕРНЫ vzhik 07.07.11 в 10:40 |
||
|
ЗАМЫКАНИЕ СОВМЕСТНО ИСПОЛЬЗУЕМЫХ ДАННЫХ vzhik 07.07.11 в 10:41 |
||
|
Попробуйте угадать что выдаст приведенный код: vzhik 07.07.11 в 10:42 |
||
|
Скорей всего Вы ошиблись, если предположили, что код выдаст числа от 0 до 9. Хотя результат может оказаться и таким, вероятнее всего на выходе будет десять десяток. Это происходит из-за правил языка относительно областей видимости и способе захвата переменных анонимными методами, который здесь использовался для указания действия в QueueUserWorkItem. Переменная i совместно используется и основным потоком, который добавляет рабочие элементы, и потоками из пула потоков, которые печатают значение i. Основной поток последовательно обновляет значение i пока проходит от 0 до 9, и поэтому каждое напечатанная линия будет содержать значение i в момент вызова из фонового потока метода Console.WriteLine. (Учтите, что в отличие от компилятора C#, компилятор Visual Basic любезно предупреждает об этой проблеме: "warning BC42324: Using the iteration variable in a lambda expression may have unexpected results. Instead, create a local variable within the loop and assign it the value of the iteration variable.") vzhik 07.07.11 в 10:47 |
||
|
Этот феномен имеет место не только в параллельном программировании, хотя положение, занимаемое анонимными методами и лямбда-выражениями в модели параллельного программирования под .NET Framework, только усиливает проблему. Рассмотрим код последовательной реализации: vzhik 07.07.11 в 14:31 |
||
|
Достоверно можно утверждать, что будет напечатано десять десяток, поскольку на момент вызова делегатов значение i уже равно 10, а все делегаты ссылаются на одну и ту же захваченную переменную. vzhik 07.07.11 в 14:48 |
||
|
Мы можем избавиться от этой проблемы созданием локальной копии итерируемой переменной в области видимости цикла (чт, между прочим, и советовал компилятор Visual Basic). Тогда у каждого анонимного метода будет собственная переменная, а не общая для всех делегатов. Небольшое изменение исправляет предыдущий пример: vzhik 07.07.11 в 14:53 |
||
|
Теперь будет напечатана последовательность от 0 до 9, как и ожидалось. Аналогичным образом исправляется и параллельная реализация: vzhik 07.07.11 в 14:54 |
||
|
Этот код также напечатает цифры от 0 до 9, хотя именно такой порядок не гарантируется. vzhik 07.07.11 в 14:56 |
||
|
Привычка объявлять переменные в начале функции, а использовать их позже в замыканиях, также может Вас запутать . Например: vzhik 07.07.11 в 15:01 |
||
|
Следуя семантике замыканий, переменная j будет совместно использоваться всеми итерациями параллельного цикла тем самым нанося серьезный ущерб внутреннему последовательному циклу. Чтобы решить эту проблему следует объявление переменной переместить как можно ближе к использованию: vzhik 07.07.11 в 15:06 |
