Актори Скали схожі на спільні програми Го?


76

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


2
Бібліотека core.async Clojure, ймовірно, більше підходить для виконання процедур, ніж akka. Окрім цього, ваша відповідь в основному суб’єктивна щодо того, що розробник хотів би використати / навчитися.
dhable

@Dan Насправді не суб'єктивний, я шукаю порівняння функцій 1: 1, тому перенесення не є головним завданням, на противагу переписанню бібліотеки б / к, мовні відмінності настільки різкі. Але у вас, мабуть, є питання ...
loyalflow

Ви можете заглянути на github.com/rssh/scala-gopher для подібних примітивів CSP у Scala.
rssh

Відповіді:


140

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

CSP еволюціонував, включивши статичну, формальну алгебру процесу для доведення існування тупикових ситуацій у коді. Це дійсно приємна функція, але ні Goroutines, ні в core.asyncданий час її не підтримують. Якщо і коли вони це роблять, буде надзвичайно приємно дізнатися перед запуском коду, чи можливий глухий кут. Однак CSP не підтримує стійкість до несправностей значущим чином, тому вам, як розробнику, доводиться з'ясовувати, як впоратися з відмовою, яка може статися по обидва боки каналів, і така логіка в кінцевому підсумку розсипається по всій програмі.

Актори, як зазначив Карл Хьюітт у 1973 році, залучають організації, які мають власну поштову скриньку. За своєю природою вони асинхронні та мають прозорість розташування, яка охоплює час роботи та машини - якщо у вас є посилання (Akka) або PID (Erlang) актора, ви можете надіслати це повідомлення. Тут також деякі люди знаходять помилку в реалізаціях, заснованих на Акторі, в тому, що Ви повинні мати посилання на іншого актора, щоб надіслати йому повідомлення, тим самим з'єднавши безпосередньо відправника та одержувача. У моделі CSP канал є спільним, і його можуть спільно використовувати різні виробники та споживачі. З мого досвіду, це не було великою проблемою. Мені подобається ідея посилань на проксі, які означають, що мій код не завалений деталями реалізації, як відправити повідомлення - я просто надсилаю одне, і де б не знаходився актор, він його отримує.

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

Актори - це також парадигма паралельності, коли актор може мати змінний стан всередині нього і гарантувати відсутність багатопотокового доступу до стану, якщо тільки розробник, побудований на основі акторської системи, випадково не введе його, наприклад, зареєструвавши Актора як слухача для зворотного дзвінка або асинхронного переходу всередину актора через ф’ючерси

Безсоромний штекер - я пишу нову книгу з керівником команди Akka Роландом Куном під назвою «Реактивні шаблони дизайну», де ми обговорюємо все це та багато іншого. Зелені нитки, CSP, цикли подій, ітерації, реактивні розширення, актори, ф’ючерси / обіцянки тощо. Очікуйте побачити План залучення персоналу до початку наступного місяця.

Удачі!


5
+0,75 :) Я думаю, що "Ні" занадто сильний. Існує подібність у сенсі передачі повідомлення. Деталі та можливості різняться, але кінцева мета досить схожа. Обидва вони прагнуть звернутися до програмування на паралельність за допомогою передачі повідомлень.
nicerobot

4
Це не нерозумно. Вони обидва асинхронні, хоча точка з'єднання продюсера каналу, що зустрічається з споживачем каналу, трохи більш пов'язана в часі, ніж модель акторів, що займаються вогнем і забуттям. Вони обидва базуються на повідомленнях. На мій погляд, актори чудово підходять для відмовостійкості та масштабування між вузлами, де CSP є ефективним механізмом використання декількох потоків у вузлі.
jamie

Ідея полягає в тому, що кожного разу, коли щось передається каналу, я міг би робити те саме те саме в Scala, але передавати це актору Scala. Мені менше до того, як все буде під ковдрою :)
loyalflow

3
@ user1361315, не зовсім вірно, що ти міг зробити те саме те саме з Akka. Канали Go часто використовуються як точки синхронізації. Ви не можете відтворити це безпосередньо в Akka. У Akka обробку після синхронізації потрібно перемістити в окремий обробник ("розсіяний" словами Джеймі: D). Я б сказав, що шаблони дизайну різні. Ви можете почати горутин із чаном, зробити щось, а потім <-почекати, поки воно закінчиться, перш ніж рухатися далі. Akka має менш потужну форму цього ask, але askнасправді це не спосіб Akka IMO. Чани також набираються, а поштові скриньки - ні.
Роб Нейпір,

1
@jamie Тож чи дійсно Akka реагує: programmers.stackexchange.com/q/255047/13154 ?
Den

57

Тут є два запитання:

  • Чи є Scala хорошим вибором для порту goroutines?

Це легке запитання, оскільки Scala - це мова загального призначення, яка не гірша чи краща за багато інших, які ви можете обрати для "перенесення горутин".

Звичайно, існує багато думок щодо того, чому Scala краща чи гірша як мова (наприклад, тут моя), але це лише думки, і не дозволяйте їм зупиняти вас. Оскільки Scala є загальним призначенням, це "в значній мірі" зводиться до: все, що ви можете робити на мові X, ви можете робити в Scala. Якщо це звучить занадто широко ... як щодо продовжень у Java :)

  • Чи схожі актори Scala goroutines?

Єдина схожість (крім нікчемності) полягає в тому, що вони обидва пов'язані з паралельністю та передачею повідомлень. Але на цьому подібність закінчується.

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

Актори допомагають "безтурботному розподілу"


Там , де «вільний занепокоєння» частина, як правило , асоціюється з такими поняттями, як: fault tolerance, resiliency, availabilityі т.д ..

Не вдаючись у важкі подробиці того, як працюють актори, двома простими словами актори мають відношення до:

  • Місцевість : кожен актор має адресу / посилання, за допомогою яких інші актори можуть надсилати повідомлення
  • Поведінка : функція, яка застосовується / викликається, коли повідомлення надходить до актора

Подумайте про "розмовні процеси", де кожен процес має посилання та функцію, яка викликається, коли надходить повідомлення.

Звичайно, є набагато більше (наприклад, перевірте Erlang OTP або akka docs ), але наведені вище два є гарним початком.

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

  • Я навмисно не використовую жаргон, такий як "посилальна прозорість", "ідемпотентність" тощо. Вони не приносять жодної користі, окрім як викликають плутанину, тому давайте просто поговоримо про незмінність [ can't change thatпоняття]. Ерланг як мова є самовпевненим, і він схиляється до сильної незмінності, тоді як у Scala занадто легко змусити акторів, які змінюють / мутують свій стан при отриманні повідомлення. Це не рекомендується, але змінність у Scala знаходиться прямо перед вами, і люди цим користуються.

  • Ще одним цікавим моментом, про який говорить Джо Армстронг, є той факт, що Scala / AKKA обмежена JVM, яка насправді не була розроблена з урахуванням "розподілу", тоді як Ерланг В.М. Це пов’язано з багатьма речами, такими як: ізоляція процесу, для кожного процесу проти цілого збору сміття VM, завантаження класів, планування процесу та інші.

Сенс вищесказаного полягає не в тому, щоб сказати, що один кращий за інший, але в тому, щоб показати, що чистота акторської моделі як концепції залежить від її реалізації.

Тепер до горутин ..

Горутини допомагають міркувати про паралельність послідовно


Як і інші відповіді, про які вже згадувалося, горутини вкорінюються у передачі послідовних процесів , що є "формальною мовою для опису моделей взаємодії в паралельних системах", що за визначенням може означати майже все :)

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

Основним примітивом паралельності в core.async / Goroutine є channel. Подумайте про це channelяк про "чергу на скелях". Цей канал використовується для "передачі" повідомлень. Будь-який процес, який хотів би "взяти участь у грі", створює або отримує посилання на a channelта передає / приймає (наприклад, надсилає / отримує) повідомлення в / з нього.

Безкоштовна цілодобова автостоянка

Більшість робіт, які виконуються на каналах, зазвичай відбуваються всередині " Goroutine " або " go block ", який " бере своє тіло і перевіряє його на будь-які операції каналу. Це перетворить тіло на автомат стану. Після досягнення будь-якої операції блокування, автомат стану буде «припаркований», і фактичний потік керування буде звільнений. Цей підхід схожий на той, що використовується в C # async. Коли операція блокування завершиться, код буде відновлений (для потоку пулу потоків або єдиний потік у JS VM) "( джерело ).

Набагато легше передати за допомогою візуального. Ось як виглядає блокування виконання вводу-виводу:

блокування вводу-виводу

Ви бачите, що нитки в основному витрачають час на очікування роботи. Ось та ж робота, але виконана за допомогою підходу "Goroutine" / "go block":

core.async

Тут 2 потоки виконали всю роботу, що зробили 4 потоки під час блокування, забираючи стільки ж часу.

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

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

Потужність Go Channel

Ще одна величезна річ у "Goroutines" / "go blocks" - це операції, які можна виконувати на каналі. Наприклад, можна створити канал очікування , який закриється через X мілісекунд. Або виберіть / alt! функція, яка, використовуючись разом із багатьма каналами, працює як механізм опитування "ти готовий" для різних каналів. Подумайте про це як про селектор сокета в неблокуючому введенні. Ось приклад використання 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 може бути не вашою рідною мовою, але ви не можете не погодитися з тим, наскільки послідовно виглядає та відчувається ця реалізація паралелізму.

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

Go UI Go!

Оскільки core.async "go blocks" має таку можливість "паркувати" стан виконання та мати дуже послідовний "вигляд і відчуття" при роботі з паралельністю, як щодо JavaScript? У JavaScript немає одночасності, оскільки існує лише одна нитка, так? І спосіб імітації одночасності - це 1024 зворотних викликів.

Але це не повинно бути так. Наведений вище приклад з wracer фактично написаний на ClojureScript, який компілюється до JavaScript. Так, це буде працювати на сервері з багатьма потоками та / або в браузері: код може залишатися незмінним.

Горутини проти core.async

Знову ж таки, кілька відмінностей у впровадженні [їх є більше], щоб підкреслити той факт, що теоретична концепція не є точно одна до одної на практиці:

  • У програмі Go канал набирається, а в core.async це не так: наприклад, у core.async ви можете розміщувати повідомлення будь-якого типу на одному каналі.
  • У Go ви можете розміщувати на каналі змінні речі. Це не рекомендується, але ви можете. У core.async, за проектом Clojure, всі структури даних є незмінними, отже, дані всередині каналів почуваються набагато безпечнішими для свого добробуту.

То який вирок?


Я сподіваюся, що вищесказане пролило світло на відмінності між акторською моделлю та CSP.

Не для того, щоб викликати полум’яну війну, а для того, щоб дати вам ще одну точку зору, скажімо, Річа Хікі:

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

Однак на практиці Whatsapp базується на Erlang OTP, і здавалося, він продається досить добре.

Ще одна цікава цитата з Роб Піке:

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

Справжня різниця між акторською моделлю та Го полягає в тому, що канали - це першокласні громадяни. Також важливо: вони є непрямими, як дескриптори файлів, а не імена файлів, дозволяючи стилі одночасності, які не так легко виразити в акторській моделі. Бувають також випадки, коли вірно і зворотне; Я не роблю ціннісного судження. Теоретично моделі еквівалентні. "( джерело )


2
О, Анатолію. Найменше, що ви могли зробити, це правильно написати моє ім'я. :)
Джеймі

3
Щодо цитати Річа Хікі, я не думаю, що примітивність черг є вагомою причиною для ентузіазму щодо акторів. Покажчики більш примітивні, ніж посилання, проте у нас немає покажчиків, розроблених для JVM.
lcn

1
Привіт. Програміст Scala вже 5 років. Користувач Akka приблизно стільки ж часу. Чим більше я використовую акторів, тим менше захоплююсь. Створити з ними дуже складні, незрозумілі системи надзвичайно просто, коли вони надмірно використовуються. Є речі, які актори роблять надзвичайно добре. ІМО, в більшості випадків, актори використовуються там, де простий цикл подій працював би так само добре чи краще.
Тім Харпер

8

Переміщення деяких моїх коментарів до відповіді. Це ставало занадто довго: D (Не відбирати від дописів Джеймі та Толітіуса; вони обидва дуже корисні відповіді.)

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

Чани також набираються, а поштові скриньки - ні. Це велика справа IMO, і це досить шокує для системи на базі Scala. Я розумію, що becomeце важко реалізувати з набраними повідомленнями, але, можливо, це вказує на те, що becomeце не дуже схоже на Scala. Я міг би сказати це про Акку взагалі. Часто це схоже на своє, що трапляється на Scala. Горутини - ключова причина існування Go.

Не зрозумійте мене неправильно; Мені дуже подобається модель актора, і мені взагалі подобається Акка, і мені приємно працювати. Мені також взагалі подобається Го (я вважаю Скалу прекрасною, тоді як Го вважаю просто корисною, але вона цілком корисна).

Але відмовостійкість - справді суть Akka IMO. Ви випадково отримуєте збіг із цим. Паралельність - це серце горутинів. Відмовостійкість - це окрема річ у Go, делегована deferі recover, яка може бути використана для реалізації досить великої кількості відмов. Відмовостійкість Акки є більш формальною та багатофункціональною, але вона може бути і дещо складнішою.

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

Отже, повертаючись до вашого фактичного запитання: я настійно рекомендую переосмислити інтерфейс Go, перш ніж переносити його на Scala або Akka (що також є абсолютно різними речами IMO). Переконайтесь, що ви робите це так, як це означає ваше ціле середовище. Прямий порт складної бібліотеки Go, швидше за все, не вписується в жодне із середовищ.


7

Це все чудові та ґрунтовні відповіді. Але простий спосіб поглянути на це, ось мій погляд. Горутини - це проста абстракція Акторів. Актори - це лише більш конкретний випадок використання Горутинів.

Ви можете реалізувати Акторів, використовуючи Горутини, створивши Горутину в сторону Каналу. Вирішивши, що канал "належить" тій Горутині, Ви говорите, що лише ця Горутина буде споживати з нього. Ваш Goroutine просто запускає цикл відповідності вхідних повідомлень на цьому каналі. Потім ви можете просто передавати Канал навколо як "адресу" свого "Актора" (Горутина).

Але оскільки Горутини є абстракцією, більш загальним дизайном, ніж актори, Горутини можна використовувати для набагато більшої кількості завдань і дизайнів, ніж Актори.

Однак компромісом є те, що оскільки "Актори" є більш конкретним випадком, реалізації таких акторів, як Erlang, можуть оптимізувати їх краще (рекурсія залізниці в циклі вхідних повідомлень) та можуть надавати інші вбудовані функції (багатопроцесорні та машинні актори) .


2

чи можна сказати, що в Actor Model адресатована сутність є Актором, одержувачем повідомлення. тоді як у каналах Go адресована сутність - це канал, канал, в який надходить повідомлення.

у каналі Go, ви надсилаєте повідомлення на канал, і будь-яка кількість одержувачів може слухати, і один з них отримає повідомлення.

в акторі повідомлення отримає лише один актор, актору якого ви посилаєте повідомлення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.