Параллелизм: как вы подходите к дизайну и отлаживаете реализацию?


37

Я разрабатываю параллельные системы уже несколько лет, и у меня есть довольно хорошее представление об этом предмете, несмотря на отсутствие формального обучения (то есть, отсутствие степени). Есть несколько новых языков, которые стали популярными, по крайней мере, в последнее время, чтобы говорить о них, предназначенных для облегчения параллелизма, таких как Erlang и Go. Похоже, что их подход к параллелизму перекликается с моим собственным опытом о том, как сделать системы масштабируемыми и использовать преимущества нескольких ядер / процессоров / машин.

Однако я считаю, что существует очень мало инструментов, которые помогут визуализировать то, что вы собираетесь делать, и убедиться, что вы хотя бы близки к своему первоначальному видению. Отладка параллельного кода может стать кошмаром для языков, которые не предназначены для параллелизма (например, C / C ++, C #, Java и т. Д.). В частности, практически невозможно воссоздать условия, которые легко возникают в одной системе в вашей среде разработки.

Итак, каковы ваши подходы к проектированию системы для параллельной и параллельной обработки? Примеры:

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

У меня есть свои ответы на некоторые из них, но я также хотел бы узнать немного больше.

редактировать

Пока у нас много хорошего вклада. Многие из статей, на которые есть ссылки, очень хороши, и я уже читал некоторые из них.

Мой личный опыт параллельного программирования заставляет меня думать, что вам нужно другое мышление, чем при последовательном программировании. Ментальный разрыв, вероятно, так же велик, как и разница между объектно-ориентированным программированием и процедурным программированием. Я хотел бы, чтобы этот набор вопросов больше фокусировался на мыслительных процессах, необходимых (то есть теории), чтобы систематически подходить к ответам. Предоставляя более конкретные ответы, это помогает привести пример - то, через что вы прошли лично.

Цель для Щедрости

Не говори мне, что я должен делать. У меня уже есть это под контролем. Скажи мне, что ты делаешь. Расскажите, как вы решаете эти проблемы.


Это хороший вопрос - много возможной глубины. Я также получил хороший опыт работы с многопоточными приложениями на Java, но хотел бы узнать больше.
Майкл К

Пока у нас есть несколько хороших ответов. Кто-нибудь хочет рискнуть тем, что вы хотите, чтобы помочь вам в этой области?
Берин Лорич

TotalView Debugger для параллельного кодирования - довольно полезный инструмент, хотя он требует
Fanatic23

Возможно, регистрация может помочь вам с двумя последними вопросами.
Амир Резаи

То, что я ищу, - это процессы людей. Это те области, где инструменты, которые я использовал, неадекватны, но могут справиться с работой. Я меньше беспокоюсь о цитировании чужой статьи и больше беспокоюсь о методологии здесь.
Берин Лорич

Ответы:


11

Я разрабатываю параллельные системы уже несколько лет, и у меня есть довольно хорошее представление об этом предмете, несмотря на отсутствие формального обучения (то есть, отсутствие степени).

Многие из лучших программистов, которых я знаю, не закончили университет. Что касается меня, я изучал философию.

C / C ++, C #, Java и т. Д.). В частности, практически невозможно воссоздать условия, которые легко возникают в одной системе в вашей среде разработки.

да

Как вы выясняете, что может быть сделано одновременно с тем, что должно быть последовательным?

мы обычно начинаем с метафоры высотой 1000 миль, чтобы прояснить нашу архитектуру для себя (во-первых) и для других (во-вторых).

Когда мы сталкивались с этой проблемой, мы всегда находили способ ограничить видимость одновременных объектов не одновременными.

В последнее время я обнаружил актеров в scala и увидел, что мои старые решения были своего рода «миниакторами», гораздо менее мощными, чем scala. Поэтому я предлагаю начать оттуда.

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

Со скалой все намного проще в любом случае.

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

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

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

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

Под Арком я имею в виду определение Нила Форда: Sw Architecture - это все, что потом будет очень сложно изменить.

программирование заставляет меня поверить, что вам нужно другое мышление, чем при последовательном программировании.

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


6

Для меня это все о данных. Правильно разбивайте свои данные, и параллельная обработка проста. Все проблемы с удержанием, тупиками и прочим уходят.

Я знаю, что это не единственный способ распараллеливания, но для меня это самый полезный способ.

Чтобы проиллюстрировать (не так быстро) историю:

Я работал над большой финансовой системой (контроль на фондовом рынке) с 2007 по 2009 год, и объем обработки данных был очень большим. Чтобы проиллюстрировать это, все вычисления, выполненные для одного отдельного аккаунта клиента, занимали около 1-3 секунд на их средней рабочей станции, и было более 30 тыс. Аккаунтов. Каждую ночь закрытие системы было большой болью для пользователей (обычно более 6 часов обработки без каких-либо ошибок для них).

Дальнейшее изучение проблемы показало, что мы можем парализовать вычисления между несколькими компьютерами, но у нас все равно будет огромное узкое место на старом сервере баз данных (сервер SQL 2000, эмулирующий SQL 6.5).

Было совершенно ясно, что наш минимальный пакет обработки был вычислением одной учетной записи, а основным узким местом было сохранение сервера базы данных (мы могли видеть на нескольких соединениях "sp_who", ожидающих такой же обработки). Таким образом, параллельный процесс пошел так:

1) Один продюсер, ответственный за последовательное чтение или запись в базу данных. Здесь нельзя использовать параллелизм. Производитель подготовил очередь рабочих мест для потребителей. База данных принадлежала исключительно этому производителю.

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

3) После расчета каждый потребитель отправлял данные обратно в синхронизированную очередь в памяти производителю, чтобы сохранить данные.

Было несколько контрольных точек, несколько механизмов, обеспечивающих правильное сохранение транзакций (ни один не был оставлен), но вся работа того стоила. В итоге расчеты, распределенные между 10 компьютерами (плюс компьютер производителя / очереди), сократили время закрытия всей системы до 15 минут.

Простое устранение проблем хранения, вызванных плохим управлением параллелизмом, SQL 6.5 дал нам большое преимущество. Все остальное было в значительной степени линейным, каждый новый компьютер, добавленный в «сетку», сокращал время обработки, пока мы не достигли «максимальной эффективности» последовательных операций чтения / записи в базе данных.


2

Работа в многопоточной среде сложна и требует дисциплины кодирования. Вы должны следовать надлежащему руководству для получения блокировки, снятия блокировки, доступа к глобальным переменным и т. Д.

Позвольте мне попытаться ответить на ваш вопрос один за другим

* How do you figure out what can be made concurrent vs. what has to be sequential?

Использовать параллелизм для

1) Опрос: - нужен поток, чтобы постоянно опрашивать что-то или отправлять обновление на регулярной основе. (Такие понятия, как сердечные биты, которые регулярно отправляют данные на центральный сервер, чтобы сказать, что я жив.)

2) Операции с тяжелым вводом / выводом могут выполняться параллельно. Лучший пример - логгер. Поток регистратора может быть отдельным потоком.

3) Схожие задачи на разных данных. Если есть какая-то задача, которая происходит с разными данными, но очень похожа по своей природе, это могут делать разные потоки. Лучшим примером будут запросы к серверу.

И, конечно, многие другие, как это в зависимости от приложения.

* How do you reproduce error conditions and view what is happening as the application executes?

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

* How do you visualize the interactions between the different concurrent parts of the application?

Поместите журналы в свои замки, чтобы вы знали, кто и когда блокирует, а кто пытался его заблокировать. Как я уже говорил ранее, попробуйте добавить идентификатор потока в журнал, чтобы понять, что происходит в каждом потоке.

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


2
  • Как вы выясняете, что может быть сделано одновременно с тем, что должно быть последовательным?

Сначала я бы спросил, будет ли приложение (или компонент) действительно получать выгоду от параллельной обработки, или, с точки зрения непрофессионала, - где узкое место? Очевидно, что параллелизм не всегда дает выгоду для инвестиций, необходимых для его работы. Если это выглядит как кандидат, то я бы работал снизу вверх - пытаясь найти самую крупную операцию или набор операций, которые могут эффективно выполнять свою работу изолированно - я не хочу раскручивать потоки для незначительного, экономически неэффективного операции - я ищу актеров .

Работая с Эрлангом, я полюбил концепцию использования асинхронной передачи сообщений и модель субъекта для параллелизма - она ​​интуитивно понятна, эффективна и чиста.

От понимания параллелизма актера

Актерская модель состоит из нескольких ключевых принципов:

  • Нет общего состояния
  • Легкие процессы
  • Асинхронная передача сообщений
  • Почтовые ящики для буферизации входящих сообщений
  • Обработка почтового ящика с сопоставлением с образцом

Актер - это процесс, который выполняет функцию. Здесь процесс - это легкий поток пользовательского пространства (не путать с типичным тяжелым процессом операционной системы). Актеры никогда не разделяют состояние и, следовательно, никогда не должны бороться за блокировки доступа к общим данным. Вместо этого актеры обмениваются данными, отправляя сообщения, которые являются неизменными. Неизменяемые данные не могут быть изменены, поэтому чтение не требует блокировки.

Модель параллелизма Erlang легче понять и отладить, чем блокировать и совместно использовать данные. То, как ваша логика изолирована, позволяет легко тестировать компоненты, передавая им сообщения.

Работа с параллельными системами - это почти то, как мой проект работал в любом случае на любом языке - очередь, из которой несколько потоков извлекали данные, выполняли простую операцию и повторяли или возвращали в очередь. Erlang просто внедряет неизменные структуры данных для предотвращения побочных эффектов и снижения стоимости и сложности создания новых потоков.

Эта модель не является эксклюзивной для Erlang, даже в мире Java и .NET существуют способы ее создания - я бы посмотрел на Concurrency and Coordination Runtime (CCR) и Relang (есть также Jetlang для Java).

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

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

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

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


1

- Мои ответы относятся к MS / Visual Studio -

Как вы выясняете, что может быть сделано одновременно с тем, что должно быть последовательным?

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

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

Много регистрации, возможность включать / выключать / включать регистрацию в производственных приложениях, чтобы поймать ее в работе. VS2010 Intellitrace должен помочь с этим, но я еще не использовал его.

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

У меня нет хорошего ответа на это, хотелось бы увидеть один.


Ведение журнала изменит способ выполнения кода и, следовательно, может привести к появлению ошибки, которую вы не обнаружили.
Мэтью Рид

1

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

Я стараюсь изо всех сил реализовывать блокировки с мыслью, что в конечном итоге действительно практичное программирование без блокировок может стать для меня реальностью. Под блокировкой я не подразумеваю взаимное исключение, я просто имею в виду процесс, который реализует безопасный параллелизм без необходимости арбитража. Под практическим я имею в виду то, что проще портировать, чем реализовать. У меня тоже очень мало формальных тренировок по CS, но я полагаю, что мне позволено желать :)

После этого большинство ошибок, с которыми я сталкиваюсь, становятся относительно мелкими, или настолько ошеломляющими, что я возвращаюсь в паб. Паб становится привлекательным вариантом только тогда, когда профилирование программы замедляет его настолько, чтобы выставить дополнительные расы, не связанные с тем, что я пытаюсь найти.

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

Затем снова добавьте туда «распределенный», и арбитраж станет обязательным. У вас есть конкретный пример?


Чтобы прояснить мое утверждение, C не был разработан специально для и вокруг параллелизма. Это контрастирует с такими языками, как Go, Erlang и Scala, которые были специально разработаны с учетом параллелизма. Я не собирался говорить, что вы не можете выполнять параллелизм с C.
Берин Лорич

1

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

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

Исходя из моего опыта, ответ на эти два аспекта заключается в следующем:

Распределенная трассировка

Распределенная трассировка - это технология, которая собирает данные о времени для каждого параллельного компонента вашей системы и представляет их вам в графическом формате. Представления о параллельных выполнениях всегда чередуются, что позволяет вам увидеть, что работает параллельно, а что нет.

Распределенная трассировка обязана своим происхождением (конечно) распределенным системам, которые по определению являются асинхронными и в высокой степени параллельными. Распределенная система с распределенной трассировкой позволяет людям:

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

Последствия распределенного отслеживания, однако, следующие:

  1. Это добавляет накладные расходы ко всем вашим параллельным процессам, поскольку переводит в дополнительный код для выполнения и отправки потенциально по сети. В некоторых случаях эти издержки очень значительны - даже Google использует свою систему отслеживания Dapper только для небольшого подмножества всех запросов, чтобы не испортить взаимодействие с пользователем.

  2. Существует много различных инструментов, не все из которых могут взаимодействовать друг с другом. Это несколько улучшается такими стандартами, как OpenTracing, но не полностью решено.

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

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

Программное обеспечение для отслеживания ошибок

Я ссылаюсь на Sentry выше, прежде всего потому, что это наиболее широко используемый инструмент, и по понятной причине - программное обеспечение для отслеживания ошибок, такое как Sentry Hijack Runtime Execution, для одновременной пересылки трассировки стека ошибок, обнаруженных на центральном сервере.

Чистая выгода такого специализированного программного обеспечения в параллельном коде:

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

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

Вы даже можете «пометить» различные аспекты вашей параллельной системы (хотя это предполагает, что у вас нет чередования работы над одним потоком, что технически не является параллельным в любом случае, так как поток просто переходит между задачами эффективно, но все равно должен обрабатывать обработчики событий до завершения) и увидеть разбивку ошибок по тегам.

  1. Вы можете изменить это программное обеспечение для обработки ошибок, чтобы предоставить дополнительную информацию с вашими исключениями времени выполнения. Какие открытые ресурсы были у процесса? Есть ли общий ресурс, который этот процесс держал? У какого пользователя возникла эта проблема?

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

  1. (Для Sentry). У вас может быть отдельная панель мониторинга отчетов Sentry для тестовых запусков системы, что позволяет вам обнаруживать ошибки при тестировании.

К недостаткам такого программного обеспечения относятся:

  1. Как и все, они добавляют объем. Например, вам может не понадобиться такая система на встроенном оборудовании. Я настоятельно рекомендую выполнить пробный запуск такого программного обеспечения, сравнив простое выполнение с несколькими выборками из бездействующего компьютера и без него.

  2. Не все языки поддерживаются одинаково, так как многие из этих систем полагаются на неявный перехват исключения, и не все языки имеют надежные исключения. При этом есть клиенты для множества систем.

  3. Они могут рассматриваться как угроза безопасности, так как многие из этих систем по сути являются закрытыми. В таких случаях проявите должную осмотрительность, исследуя их, или, если хотите, сверните свои собственные.

  4. Они не всегда могут дать вам необходимую информацию. Это риск со всеми попытками добавить видимость.

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

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

Некоторые дополнительные предложения

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

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

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

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

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

  • Используйте правильные алгоритмы для проверки состояния вашего приложения. Я говорю о простых вещах, таких как, когда у вас есть мастер-процесс, ожидающий, пока все его работники завершат задачу, и вы хотите перейти к следующему шагу, только если все работники полностью выполнены - это пример обнаружения глобального завершение, для которого существуют известные методологии, такие как алгоритм Safra.

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

Общее практическое правило: проектирование на отказ в параллельных системах . Ожидайте, что общие службы будут аварийно завершаться или ломаться. Это относится даже к коду, который не распределяется по компьютерам - одновременный код на одном компьютере может зависеть от внешних зависимостей (таких как общий файл журнала, сервер Redis, проклятый сервер MySQL), которые могут исчезнуть или быть удалены в любое время ,

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

Как вы понимаете, что можно сделать параллельным, а что можно сделать последовательным?

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

Без метрик вы не сможете сделать несколько очень важных вещей:

  1. Оцените разницу, внесенную изменениями в системе. Если вы не знаете, что при настройке ручки A метрика B повышается, а метрика C снижается, вы не знаете, как исправить вашу систему, когда люди вставляют неожиданно злонамеренный код в вашу систему (и они передают код в вашу систему) ,

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

Метрики настолько важны и важны, что я сознательно попытался спланировать то, что я хочу измерить, прежде чем даже думать о том, что потребуется системе. На самом деле, метрики настолько важны, что я считаю, что они являются правильным ответом на этот вопрос: вы знаете только то, что может быть сделано последовательным или параллельным, когда вы измеряете, что делают биты в вашей программе. Правильный дизайн использует числа, а не догадки.

При этом, безусловно, есть несколько практических правил:

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

  2. Никогда не смешивайте задачу, связанную с вводом / выводом, с задачей, связанной с ЦП, в том же ядре. Не (например) не пишите веб-сканер, который запускает десять одновременных запросов в одном потоке, очищает их, как только они поступают, и не рассчитывает на масштабирование до пятисот - запросы ввода-вывода идут в очередь параллельно, но процессор все равно будет проходить через них последовательно. (Эта однопотоковая модель, управляемая событиями, является популярной, но она ограничена из-за этого аспекта - вместо того, чтобы понять это, люди просто складывают руки и говорят, что Node не масштабируется, чтобы дать вам пример).

Один поток может выполнять много операций ввода-вывода. Но чтобы полностью использовать параллелизм вашего оборудования, используйте пулы потоков, которые вместе занимают все ядра. В приведенном выше примере запуск пяти процессов Python (каждый из которых может использовать ядро ​​на шестиядерной машине) только для работы с процессором, а шестой поток Python только для работы ввода-вывода будет масштабироваться гораздо быстрее, чем вы думаете.

Единственный способ использовать преимущества параллелизма ЦП - выделенный пул потоков. Один поток часто достаточно хорош для большого количества операций ввода-вывода. Вот почему управляемые событиями веб-серверы, такие как Nginx, масштабируются лучше (они выполняют работу, связанную исключительно с вводом / выводом), чем Apache (который связывает работу, связанную с вводом / выводом, с чем-то, требующим ЦП, и запускает процесс по запросу), но зачем использовать Node для выполнения десятки тысяч вычислений на GPU, полученных параллельно, - ужасная идея.


0

Что ж, для процесса проверки, при проектировании большой параллельной системы - я склонен тестировать модель с помощью LTSA - Labeled Transition System Analyzer . Он был разработан моим старым наставником, который является ветераном в области параллелизма и в настоящее время является руководителем отдела вычислительной техники в Imperial.

Что касается выяснения того, что может и не может быть одновременным, существуют статические анализаторы, которые могут это показать, я верю, хотя я склонен просто рисовать графики планирования для критических секций, так же, как вы делаете это для управления проектами. Затем определите разделы, которые выполняют одну и ту же операцию несколько раз. Быстрый путь - просто найти петли, поскольку они, как правило, являются областями, которые выигрывают от параллельной обработки.


0

Как вы выясняете, что может быть сделано одновременно с тем, что должно быть последовательным?

Практически каждая вещь, которую вы пишете, может использовать преимущества параллелизма, особенно сценарий использования «поделить победителем». Гораздо лучший вопрос: что должно быть одновременно?

Потоки Джозефа Албахари в C # перечисляют пять наиболее распространенных применений.

Многопоточность имеет много применений; вот самые распространенные:

Поддержание адаптивного пользовательского интерфейса

Запуская трудоемкие задачи в параллельном «рабочем» потоке, основной поток пользовательского интерфейса может продолжить обработку событий клавиатуры и мыши.

Эффективное использование иначе заблокированного процессора

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

Параллельное программирование

Код, который выполняет интенсивные вычисления, может выполняться быстрее на многоядерных или многопроцессорных компьютерах, если рабочая нагрузка распределяется между несколькими потоками в стратегии «разделяй и властвуй» (см. Часть 5).

Спекулятивное исполнение

На многоядерных машинах иногда можно улучшить производительность, прогнозируя что-то, что может потребоваться сделать, а затем делая это заранее. LINQPad использует эту технику для ускорения создания новых запросов. Вариант заключается в параллельном запуске нескольких различных алгоритмов, которые решают одну и ту же задачу. Какой бы из них ни закончил, первые «выигрывают» - это эффективно, когда вы не знаете заранее, какой алгоритм будет выполняться быстрее всего.

Разрешение обрабатывать запросы одновременно

На сервере клиентские запросы могут поступать одновременно и поэтому должны обрабатываться параллельно (.NET Framework создает потоки для этого автоматически, если вы используете ASP.NET, WCF, веб-службы или удаленное взаимодействие). Это также может быть полезно на клиенте (например, для обработки одноранговых сетей или даже нескольких запросов от пользователя).

Если вы не пытаетесь сделать что-то из вышеперечисленного, вам лучше подумать об этом.

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

Если вы используете .NET и написали сценарии использования, вы можете использовать CHESS, который может воссоздать определенные условия чередования потоков, что позволяет вам протестировать исправление.

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

Это зависит от контекста. Для рабочих сценариев я думаю о менеджере-подчиненном. Менеджер говорит подчиненному что-то сделать и ждет обновления статуса.

Для одновременных несвязанных задач я думаю о лифтах или автомобилях в отдельных полосах движения.

Для синхронизации я иногда думаю о светофорах или стилях поворота.

Также, если вы используете C # 4.0, вы можете взглянуть на Task Parallel Library


0

Мой ответ на эти вопросы:

  • Как вы выясняете, что может быть сделано одновременно с тем, что должно быть последовательным?

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

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

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

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

Я не эксперт в этом вопросе, но я думаю, что для параллельных систем это не правильный подход. Следует выбрать теоретический подход с учетом 4 тупиковых требований в критических областях:

  1. Не преимущественное право
  2. Держись и жди
  3. Взаимное исключение
  4. Круговая цепь

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

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


0

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

Тогда я начинаю кодировать.
Просто шучу. Сначала немного больше исследований. Мне нравится сидеть с коллегой-хакером и проходить ожидаемое выполнение программы на высоком уровне. Если возникают вопросы, мы переходим на более низкий уровень. Важно выяснить, может ли кто-то еще понять ваше решение достаточно хорошо, чтобы поддержать его.

Наконец-то я начинаю кодировать. Сначала я стараюсь сделать это очень просто. Просто путь к коду, ничего особенного. Переместить как можно меньше состояния. Избегайте пишет. Избегайте чтения, которое может конфликтовать с записью. Избегайте, прежде всего, записей, которые могут конфликтовать с записями. Очень легко обнаружить, что у вас есть положительно ядовитое число из них, и что ваше прекрасное решение внезапно становится чуть более чем последовательным подходом, который побеждает в кэш-памяти.

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

Наконец, инструменты. Отладка очень сложная. Я использую valgrind \ callgrind в Linux вместе с PIN и параллельные студии на Windows. Не пытайтесь отлаживать этот материал вручную. Вы, вероятно, можете. Но вы, вероятно, хотели бы, чтобы вы этого не сделали. Десять часов освоения некоторых мощных инструментов, а некоторые хорошие модели сэкономят вам сотни часов спустя.

Прежде всего, работать постепенно. Работайте осторожно. Не пишите параллельный код, когда устали. Не пиши, пока голоден. На самом деле, если вы можете избежать этого, просто не пишите это. Параллельность сложна, и я обнаружил, что многие приложения, которые перечисляют ее как функцию, часто поставляются с ней как с единственной функцией.

В итоге:
начать:
думать тест
говорить писать просто читать тест писать отладку GOTO начать






Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.