Чем параллельные языки для передачи сообщений на практике лучше, чем параллельные языки с общей памятью на практике?

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

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

4 ответа

Решение

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

Актеры

Актеры находят свою наиболее заметную реализацию в языке Erlang, который, насколько я знаю, где и началась идея *. Эрланг разработан с нуля вокруг актеров. Идея состоит в том, что сами актеры являются черными ящиками друг для друга; они взаимодействуют только путем передачи сообщений.

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

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

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

Программная транзакционная память

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

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

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

В Scala есть библиотека STM, которая не является частью стандартной библиотеки, но рассматривается на предмет ее включения. Clojure и Haskell имеют хорошо разработанные библиотеки STM.

Автоматическое распараллеливание

Автоматическое распараллеливание предполагает, что вы не хотите думать о параллелизме; Вы просто хотите, чтобы все произошло быстро. Поэтому, если у вас есть какая-то параллельная операция - например, применение какой-либо сложной операции к коллекции элементов, по одному, и создание некоторой другой коллекции в результате - у вас должны быть подпрограммы, которые автоматически делают это параллельно. Коллекции Scala могут быть использованы таким образом (есть .par метод, который преобразует обычную последовательную коллекцию в ее параллельный аналог). Многие другие языки имеют аналогичные функции (Clojure, Matlab и т. Д.).


Редактировать: На самом деле модель актера была описана еще в 1973 году и, вероятно, была мотивирована более ранней работой в Simula 67 (использование сопрограмм вместо параллелизма); в 1978 году произошли связанные последовательные процессы общения. Таким образом, возможности Эрланга не были уникальными в то время, но язык был уникально эффективен при развертывании модели актера.

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

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

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

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

Для меня использование акторов Scala (Akka) имело несколько преимуществ по сравнению с традиционными моделями параллелизма:

  1. Использование системы передачи сообщений, такой как актеры, позволяет легко управлять общим состоянием. Например, я часто буду оборачивать изменяемую структуру данных в актере, поэтому единственный способ получить к ней доступ - это передача сообщений. Поскольку субъекты всегда обрабатывают одно сообщение за раз, это гарантирует, что все операции с данными являются поточно-ориентированными.
  2. Актеры частично устраняют необходимость иметь дело с порождением и поддержанием потоков. Большинство библиотек актеров справляются с распределением актеров по потокам, поэтому вам нужно только беспокоиться о запуске и остановке актеров. Часто я создаю серию идентичных актеров, по одному на физическое ядро ​​ЦП, и использую актер балансировки нагрузки для равномерного распределения им сообщений.
  3. Актеры могут помочь повысить надежность системы. Я использую актеров Akka, и одна из них заключается в том, что вы можете создать супервизор для актеров, где в случае сбоя актера супервизор автоматически создаст новый экземпляр. это может помочь предотвратить ситуации с многопоточностью, когда поток падает, и вы застряли с наполовину запущенной программой. Также очень легко раскрутить новых актеров по мере необходимости и работать с удаленными актерами, работающими в другом приложении.

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

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

Другие вопросы по тегам