Подтвердить что ты не робот

Являются ли Scala актеры похожими на Go coroutines?

Если бы мне захотелось портировать библиотеку Go, которая использует Goroutines, будет ли Scala хорошим выбором, потому что его структура inbox/akka похожа по своей природе на сопрограммы?

4b9b3361

Ответ 1

Нет, это не так. Горутины основаны на теории передачи последовательных процессов, как это было указано Тони Хоаре в 1978 году. Идея состоит в том, что могут быть два процесса или нити, которые действуют независимо друг от друга, но совместно используют "канал", который один процесс/поток помещает данные в и другой процесс/поток потребляет. Наиболее известными реализациями, которые вы найдете, являются каналы Go и Clojure core.async, но в настоящее время они ограничены текущей средой выполнения и не могут быть распределены даже между двумя режимами времени в одном физическом поле.

CSP эволюционировал, чтобы включить статическую формальную алгебру процесса для доказательства существования взаимоблокировок в коде. Это действительно хорошая функция, но ни Goroutines, ни core.async в настоящее время ее не поддерживают. Если и когда они это сделают, будет очень приятно знать, прежде чем запускать ваш код, независимо от того, возможен ли взаимоблокировка. Тем не менее, CSP не поддерживает отказоустойчивость значимым образом, поэтому вы, как разработчик, должны выяснить, как справляться с сбоем, который может произойти с обеих сторон каналов, и такая логика заканчивается тем, что она разбросана по всему приложению.

Актеры, как указано Карлом Хьюиттом в 1973 году, включают объекты, у которых есть свой почтовый ящик. Они асинхронны по своей природе и имеют прозрачность местоположения, которая охватывает продолжительность и машины - если у вас есть ссылка (Akka) или PID (Erlang) актера, вы можете сообщить об этом. Это также приводит к тому, что некоторые люди придираются к реалиям на основе актеров, поскольку вам нужно иметь ссылку на другого актера, чтобы отправить ему сообщение, тем самым напрямую связав отправителя и получателя. В модели CSP канал является общим и может использоваться несколькими производителями и потребителями. По моему опыту, это не было большой проблемой. Мне нравится идея ссылок на прокси-серверы, что означает, что мой код не усеян деталями реализации, как отправить сообщение - я просто отправлю его, и где бы он ни находился, он получает его. Если этот node спускается, и актер перевоплощается в другое место, он теоретически прозрачен для меня.

У актёров есть еще одна очень приятная функция - отказоустойчивость. Организовывая участников в иерархию контроля по спецификации OTP, разработанной в Erlang, вы можете создать домен отказа в вашем приложении. Подобно классам ценности/DTO/тому, что вы хотите назвать, вы можете моделировать сбой, как его обрабатывать и на каком уровне иерархии. Это очень мощно, так как у вас очень мало возможностей обработки ошибок внутри CSP.

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

Бесстыдный плагин - я пишу новую книгу с главой команды Akka, Роландом Кеном, называется "Реактивные образцы дизайна", где мы обсуждаем все это и многое другое. Зеленые потоки, CSP, циклы событий, Iteratees, Reactive Extensions, Actors, Futures/ Promises и т.д. Ожидайте увидеть MEAP на Manning в начале следующего месяца.

Удачи!

Ответ 2

Здесь есть два вопроса:

  • Является ли Scala хорошим выбором для порта goroutines?

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

Есть, конечно, много мнений о том, почему Scala лучше или хуже, чем язык (например, here это мое), но это всего лишь мнения, и не позволяйте им останавливать вас. Поскольку Scala является общим назначением, оно "в значительной степени" сводится к: все, что вы можете сделать в языке X, вы можете сделать в Scala. Если это звучит слишком широко... как насчет продолжения в Java:)

  • Актеры Scala похожи на goroutines?

Единственное сходство (в стороне от nitpicking) заключается в том, что оба они связаны с concurrency и передачей сообщений. Но именно здесь заканчивается сходство.

Так как ответ Джейми дал хороший обзор Scala актеров, я сосредоточусь больше на Goroutines/core.async, но с некоторым вступлением в актерскую модель.

Актеры помогают вещам быть свободными от беспокойства


В тех случаях, когда кусок "без беспокойства" обычно ассоциируется с такими терминами, как: fault tolerance, resiliency, availability и т.д.

Не вдаваясь в подробности о том, как работают актеры, в двух простых терминах актеры имеют отношение к:

  • Локальность: каждый актер имеет адрес/ссылку, которую другие участники могут использовать для отправки сообщений на
  • Поведение: функция, которая применяется/вызывается, когда сообщение поступает к актеру

Думайте "говорящие процессы", где каждый процесс имеет ссылку и функцию, которая вызывается, когда приходит сообщение.

Конечно, есть намного больше (например, проверить Erlang OTP или akka docs), но вышеприведенные два являются хорошим началом.

Там, где интересно, с актерами есть.. реализация. На данный момент два больших: Erlang OTP и Scala AKKA. Хотя оба они стремятся решить одно и то же, есть некоторые различия. Давайте посмотрим на пару:

  • Я намеренно не использую lingo, такие как "ссылочная прозрачность", "идемпотентность" и т.д., они не делают ничего хорошего, кроме путаницы, поэтому позвольте просто поговорить о неизменности [a can't change that concept]. Эрланг как язык упрям, и он склоняется к сильной неизменности, в то время как в Scala слишком легко сделать актеров, которые изменяют/мутируют свое состояние при получении сообщения. Это не рекомендуется, но изменчивость в Scala находится прямо перед вами, и люди ее используют.

  • Еще один интересный момент, о котором говорит Джо Армстронг, заключается в том, что Scala/AKKA ограничен JVM, который просто не был разработан с учетом "распределения", в то время как Erlang VM был. Он имеет отношение ко многим вещам, таким как: изоляция процесса, по каждому процессу против всей коллекции мусора VM, загрузка классов, планирование процессов и другие.

Точка вышеизложенного не означает, что одна лучше другой, но она показывает, что чистота модели актера как концепции зависит от ее реализации.

Теперь на goroutines..

Гороуты помогают рассуждать о concurrency последовательно


Как уже упоминалось в других ответах, goroutines уходит корнями в Коммуникация последовательных процессов, который является "формальным языком для описания моделей взаимодействия в параллельных систем", которые по определению могут означать почти все:)

Я приведу примеры на основе core.async, так как я знаю его внутренности лучше, чем Goroutines. Но core.async был построен после модели Goroutines/CSP, поэтому концепций не должно быть слишком много.

Основной примитив concurrency в core.async/Goroutine равен channel. Подумайте о channel как о "очереди на камнях". Этот канал используется для передачи сообщений. Любой процесс, который хотел бы "участвовать в игре", создает или получает ссылку на channel и помещает/принимает (например, отправляет/принимает) сообщения в/из него.

Бесплатная круглосуточная парковка

Большая часть работы, выполняемой по каналам, обычно происходит внутри Goroutine "или" go block ", который" берет свое тело и исследует его для любых операций с каналом. Он превратит тело в государственную машину. По достижении какой-либо операции блокировки конечный автомат будет "припаркован", и фактический поток управления будет освобожден. Этот подход аналогичен используемому в С# async. Когда операция блокировки завершается, код будет возобновлен (в потоке пула потоков или в единственном потоке в JS VM) "(source).

Гораздо легче передать визуальным. Вот как выглядит блокирующее выполнение ввода-вывода:

blocking IO

Вы можете видеть, что потоки в основном тратят время на ожидание работы. Вот такая же работа, но сделана с помощью подхода "Горутин" / "идти":

core.async

Здесь 2 потока выполнили всю работу, что 4 потока сделали в блокирующем подходе, и заняли одинаковое количество времени.

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

note: в core.async канал может использоваться за пределами блока "go block", который будет поддерживаться потоком JVM без возможности парковки: например. если он блокирует, он блокирует реальный поток.

Мощность канала Go

Еще одна огромная вещь в "Goroutines" / "go blocks" - это операции, которые могут выполняться на канале. Например, можно создать таймаут-канал, который закроется через X миллисекунд. Или выберите /alt! функция, которая при использовании в сочетании со многими каналами работает как механизм "готов" к различным каналам, Подумайте об этом как селектор сокета в неблокирующем IO. Ниже приведен пример использования timeout channel и alt! вместе:

(defn race [q]
  (searching [:.yahoo :.google :.bing])
  (let [t (timeout timeout-ms)
        start (now)]
    (go
      (alt! 
        (GET (str "/yahoo?q=" q))  ([v] (winner :.yahoo v (took start)))
        (GET (str "/bing?q=" q))   ([v] (winner :.bing v (took start)))
        (GET (str "/google?q=" q)) ([v] (winner :.google v (took start)))
        t                          ([v] (show-timeout timeout-ms))))))

Этот фрагмент кода взят из wracer, где он отправляет один и тот же запрос всем трем: Yahoo, Bing и Google, и возвращает результат из самого быстрого или истекает время (возвращает сообщение о тайм-ауте), если ни один из них не возвращается в течение заданного времени. Clojure может быть не вашим первым языком, но вы не можете не согласиться с тем, как выглядит последовательная реализация этой функции concurrency.

Вы также можете объединять/вставлять/разворачивать данные из/во многие каналы, отображать/уменьшать/фильтровать/... данные каналов и многое другое. Каналы также являются гражданами первого класса: вы можете передать канал на канал.

Go UI Go!

Так как core.async "go blocks" имеет эту способность "припарковать" состояние выполнения и имеет очень последовательный "внешний вид" при работе с concurrency, как насчет JavaScript? В JavaScript нет concurrency, так как существует только один поток, не так ли? И образ concurrency передразнивается через 1024 обратных вызова.

Но это не должно быть так. Вышеприведенный пример из wracer на самом деле написан на ClojureScript, который сводится к JavaScript. Да, он будет работать на сервере со многими потоками и/или в браузере: код может оставаться неизменным.

Goroutines vs. core.async

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

  • В Go набирается канал, в core.async это не: например. в core.async вы можете помещать сообщения любого типа в один и тот же канал.
  • В Go вы можете поместить измененные вещи на канал. Это не рекомендуется, но вы можете. В core.async, дизайне Clojure все структуры данных неизменяемы, поэтому данные внутри каналов чувствуют себя намного безопаснее для его благополучия.

Итак, какой вердикт?


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

Не вызвать пламенную войну, но дать вам еще одну перспективу, пусть скажет Рич Хики:

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

Однако на практике Whatsapp основан на Erlang OTP, и он, похоже, очень хорошо продается.

Еще одна интересная цитата от Роб Пайка:

"Буферизованные отправления не подтверждены отправителю и могут принимать произвольно длинные буферизованные каналы и goroutines очень близки к модели актера.

Реальная разница между моделью актера и Go заключается в том, что каналы являются первоклассными гражданами. Также важно: они являются косвенными, такими как файловые дескрипторы, а не имена файлов, допускающие стили concurrency, которые не так легко выражаются в модели актера. Есть также случаи, когда верно обратное; Я не принимаю оценочное суждение. Теоретически модели эквивалентны. "(источник)

Ответ 3

Перемещение некоторых моих комментариев в ответ. Слишком долго: D (Не отнимать сообщения от jamie и tolitius, они оба очень полезные ответы.)

Не совсем верно, что вы можете делать то же самое, что и с горутами в Акке. Каналы перехода часто используются в качестве точек синхронизации. Вы не можете воспроизвести это прямо в Акке. В Akka обработка постсинхронизации должна быть перенесена в отдельный обработчик ( "разбросанный" в словах jamie: D). Я бы сказал, что шаблоны дизайна разные. Вы можете запустить goroutine с помощью chan, сделать некоторые вещи, а затем <- дождаться, пока он закончит, прежде чем двигаться дальше. Акка имеет менее мощную форму этого с ask, но ask на самом деле не является способом Akka способом IMO.

Также перечислены Chans, а почтовые ящики - нет. Это большое дело ИМО, и это довольно шокирует систему на основе Scala. Я понимаю, что become сложно реализовать с типизированными сообщениями, но, возможно, это означает, что become не очень похож на Scala. Я мог бы сказать об Акке вообще. Он часто чувствует себя как свое, что происходит на Scala. Горутины являются ключевой причиной существования Go.

Не поймите меня неправильно; Мне нравится актерская модель, и мне нравится Akka и мне приятно работать. Мне также нравится Go (я нахожу Scala красивым, в то время как я нахожу Go просто полезным, но это очень полезно).

Но отказоустойчивость действительно является точкой Акка ИМО. Вы получаете concurrency с этим. concurrency - сердце горутин. Толерантность к отказам - это отдельная вещь в Go, делегированная defer и recover, которая может быть использована для реализации целого ряда отказов. Отказоустойчивость от Akka более формальна и функциональна, но она также может быть немного сложнее.

Все сказали, что, несмотря на некоторое сходство, Akka не является надмножеством Go, и они имеют значительное расхождение в функциях. Акка и Го совершенно разные в том, как они побуждают вас к решению проблем, а вещи, которые легки в одном, неудобны, непрактичны или, по крайней мере, не идиоматичны в другом. И что ключевые отличительные признаки в любой системе.

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

Ответ 4

Все это большие и полные ответы. Но для простого способа взглянуть на это, вот мое мнение. Горутины - это простая абстракция Актеров. Актеры - это более конкретный вариант использования Goroutines.

Вы можете реализовать Актеров, используя Goroutines, создав Goroutine в стороне от канала. Решив, что канал "принадлежит" этим Горутином, вы говорите, что из этого будет потреблять только Goroutine. Ваш Goroutine просто запускает контур соответствия входящих сообщений на этом канале. Затем вы можете просто передать канал вокруг как "адрес" вашего "Актера" (Goroutine).

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

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