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

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

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

Ответ 1

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

Актеры

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

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

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

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

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

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

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

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

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

Автоматическая параллелизация

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


Изменить: На самом деле, Модель актера была описана еще в 1973 году и, вероятно, была мотивирована предыдущей работой в Simula 67 (используя сопрограммы вместо concurrency); в 1978 году появился связанный Коммуникация последовательных процессов. Таким образом, возможности Erlang не были уникальными в то время, но язык был уникальным для развертывания модели актера.

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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