Якби я хотів перенести бібліотеку Go, яка використовує Goroutines, чи був би Scala хорошим вибором, оскільки його вхідні / akka-рамки схожі за своєю суттю на програми?
Якби я хотів перенести бібліотеку Go, яка використовує Goroutines, чи був би Scala хорошим вибором, оскільки його вхідні / akka-рамки схожі за своєю суттю на програми?
Відповіді:
Ні, вони ні. Горутини засновані на теорії передачі послідовних процесів, як зазначено Тоні Хоаром у 1978 р. Ідея полягає в тому, що можуть бути два процеси або потоки, які діють незалежно один від одного, але мають спільний "канал", який один процес / потік розміщує дані в, а інший процес / потік споживає. Найвідоміші реалізації, які ви знайдете, - це канали Go та Clojure core.async
, але наразі вони обмежені поточним часом виконання і не можуть бути розподілені навіть між двома робочими середовищами в одному фізичному вікні.
CSP еволюціонував, включивши статичну, формальну алгебру процесу для доведення існування тупикових ситуацій у коді. Це дійсно приємна функція, але ні Goroutines, ні в core.async
даний час її не підтримують. Якщо і коли вони це роблять, буде надзвичайно приємно дізнатися перед запуском коду, чи можливий глухий кут. Однак CSP не підтримує стійкість до несправностей значущим чином, тому вам, як розробнику, доводиться з'ясовувати, як впоратися з відмовою, яка може статися по обидва боки каналів, і така логіка в кінцевому підсумку розсипається по всій програмі.
Актори, як зазначив Карл Хьюітт у 1973 році, залучають організації, які мають власну поштову скриньку. За своєю природою вони асинхронні та мають прозорість розташування, яка охоплює час роботи та машини - якщо у вас є посилання (Akka) або PID (Erlang) актора, ви можете надіслати це повідомлення. Тут також деякі люди знаходять помилку в реалізаціях, заснованих на Акторі, в тому, що Ви повинні мати посилання на іншого актора, щоб надіслати йому повідомлення, тим самим з'єднавши безпосередньо відправника та одержувача. У моделі CSP канал є спільним, і його можуть спільно використовувати різні виробники та споживачі. З мого досвіду, це не було великою проблемою. Мені подобається ідея посилань на проксі, які означають, що мій код не завалений деталями реалізації, як відправити повідомлення - я просто надсилаю одне, і де б не знаходився актор, він його отримує.
У акторів є ще одна дуже приємна особливість - відмовостійкість. Організуючи акторів в ієрархію нагляду відповідно до специфікації OTP, розробленої в Erlang, ви можете вбудувати домен відмов у вашу програму. Подібно до класів значень / DTO / як би ви не хотіли їх називати, ви можете змоделювати збій, як з ним слід поводитися і на якому рівні ієрархії. Це дуже потужно, оскільки у вас дуже мало можливостей усунення несправностей всередині CSP.
Актори - це також парадигма паралельності, коли актор може мати змінний стан всередині нього і гарантувати відсутність багатопотокового доступу до стану, якщо тільки розробник, побудований на основі акторської системи, випадково не введе його, наприклад, зареєструвавши Актора як слухача для зворотного дзвінка або асинхронного переходу всередину актора через ф’ючерси
Безсоромний штекер - я пишу нову книгу з керівником команди Akka Роландом Куном під назвою «Реактивні шаблони дизайну», де ми обговорюємо все це та багато іншого. Зелені нитки, CSP, цикли подій, ітерації, реактивні розширення, актори, ф’ючерси / обіцянки тощо. Очікуйте побачити План залучення персоналу до початку наступного місяця.
Удачі!
<-
почекати, поки воно закінчиться, перш ніж рухатися далі. Akka має менш потужну форму цього ask
, але ask
насправді це не спосіб Akka IMO. Чани також набираються, а поштові скриньки - ні.
Тут є два запитання:
goroutines
?Це легке запитання, оскільки Scala - це мова загального призначення, яка не гірша чи краща за багато інших, які ви можете обрати для "перенесення горутин".
Звичайно, існує багато думок щодо того, чому Scala краща чи гірша як мова (наприклад, тут моя), але це лише думки, і не дозволяйте їм зупиняти вас. Оскільки Scala є загальним призначенням, це "в значній мірі" зводиться до: все, що ви можете робити на мові X, ви можете робити в Scala. Якщо це звучить занадто широко ... як щодо продовжень у Java :)
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":
Тут 2 потоки виконали всю роботу, що зробили 4 потоки під час блокування, забираючи стільки ж часу.
Кікер у наведеному вище описі: "потоки припарковані ", коли у них немає роботи, а це означає, що їх стан "розвантажується" до автомата стану, а фактичний поточний потік JVM може виконувати іншу роботу ( джерело для чудового візуального )
Примітка : у core.async канал можна використовувати поза "go block", який буде підтримуватися потоком JVM без можливості паркування: наприклад, якщо він блокує, він блокує реальний потік.
Ще одна величезна річ у "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 може бути не вашою рідною мовою, але ви не можете не погодитися з тим, наскільки послідовно виглядає та відчувається ця реалізація паралелізму.
Ви також можете об'єднати / роздути / розігнати дані з / у багато каналів, зіставити / зменшити / фільтрувати / ... дані каналів та багато іншого. Канали також є громадянами першого класу: ви можете передати канал на канал ..
Оскільки core.async "go blocks" має таку можливість "паркувати" стан виконання та мати дуже послідовний "вигляд і відчуття" при роботі з паралельністю, як щодо JavaScript? У JavaScript немає одночасності, оскільки існує лише одна нитка, так? І спосіб імітації одночасності - це 1024 зворотних викликів.
Але це не повинно бути так. Наведений вище приклад з wracer фактично написаний на ClojureScript, який компілюється до JavaScript. Так, це буде працювати на сервері з багатьма потоками та / або в браузері: код може залишатися незмінним.
Знову ж таки, кілька відмінностей у впровадженні [їх є більше], щоб підкреслити той факт, що теоретична концепція не є точно одна до одної на практиці:
Я сподіваюся, що вищесказане пролило світло на відмінності між акторською моделлю та CSP.
Не для того, щоб викликати полум’яну війну, а для того, щоб дати вам ще одну точку зору, скажімо, Річа Хікі:
" Я досі залишаюсь без ентузіазму щодо акторів. Вони все ще поєднують продюсера зі споживачем. Так, можна імітувати або впроваджувати певні види черг з акторами (і, зокрема, люди це часто роблять), але оскільки будь-який механізм актора вже включає чергу, це здається очевидним, що черги є більш примітивними. Слід зазначити, що механізми Clojure для одночасного використання держави залишаються життєздатними, а канали орієнтовані на потокові аспекти системи "( джерело )
Однак на практиці Whatsapp базується на Erlang OTP, і здавалося, він продається досить добре.
Ще одна цікава цитата з Роб Піке:
" Буферизовані надсилання не підтверджуються відправнику і можуть тривати довільно довго. Буферовані канали та програми дуже близькі до акторської моделі.
Справжня різниця між акторською моделлю та Го полягає в тому, що канали - це першокласні громадяни. Також важливо: вони є непрямими, як дескриптори файлів, а не імена файлів, дозволяючи стилі одночасності, які не так легко виразити в акторській моделі. Бувають також випадки, коли вірно і зворотне; Я не роблю ціннісного судження. Теоретично моделі еквівалентні. "( джерело )
Переміщення деяких моїх коментарів до відповіді. Це ставало занадто довго: 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, швидше за все, не вписується в жодне із середовищ.
Це все чудові та ґрунтовні відповіді. Але простий спосіб поглянути на це, ось мій погляд. Горутини - це проста абстракція Акторів. Актори - це лише більш конкретний випадок використання Горутинів.
Ви можете реалізувати Акторів, використовуючи Горутини, створивши Горутину в сторону Каналу. Вирішивши, що канал "належить" тій Горутині, Ви говорите, що лише ця Горутина буде споживати з нього. Ваш Goroutine просто запускає цикл відповідності вхідних повідомлень на цьому каналі. Потім ви можете просто передавати Канал навколо як "адресу" свого "Актора" (Горутина).
Але оскільки Горутини є абстракцією, більш загальним дизайном, ніж актори, Горутини можна використовувати для набагато більшої кількості завдань і дизайнів, ніж Актори.
Однак компромісом є те, що оскільки "Актори" є більш конкретним випадком, реалізації таких акторів, як Erlang, можуть оптимізувати їх краще (рекурсія залізниці в циклі вхідних повідомлень) та можуть надавати інші вбудовані функції (багатопроцесорні та машинні актори) .
чи можна сказати, що в Actor Model адресатована сутність є Актором, одержувачем повідомлення. тоді як у каналах Go адресована сутність - це канал, канал, в який надходить повідомлення.
у каналі Go, ви надсилаєте повідомлення на канал, і будь-яка кількість одержувачів може слухати, і один з них отримає повідомлення.
в акторі повідомлення отримає лише один актор, актору якого ви посилаєте повідомлення.