Актори Scala: отримуйте проти реакції


110

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

Однак я читав про програму Scaler's Actor в програмуванні в Scala , і є одне, чого я не розумію. У главі 30.4 сказано, що використання reactзамість цього receiveдозволяє повторно використовувати потоки, що добре для продуктивності, оскільки потоки є дорогими в JVM.

Чи означає це, що поки я пам'ятаю дзвонити reactзамість receive, я можу почати стільки акторів, скільки мені подобається? Перш ніж відкрити Scala, я грав з Erlang, і автор програми програмування Erlang може похвалитися тим, що нерестується понад 200 000 процесів, не порушуючи поту. Мені б не хотілося це робити з потоками Java. На які обмеження я дивлюсь у Scala порівняно з Erlang (та Java)?

Крім того, як ця нитка повторного використання працює у Scala? Припустимо, для простоти, що у мене є лише одна нитка. Чи будуть усі учасники, яких я починаю послідовно виконувати в цій темі, чи відбудеться якесь переключення завдань? Наприклад, якщо я запускаю двох акторів, які надсилають один одному повідомлення з пінг-понгу, чи буду ризикувати тупиком, якщо вони будуть запущені в одній темі?

Згідно програмування в Scala , писати акторів для використання reactскладніше, ніж з receive. Це звучить правдоподібно, оскільки reactне повертається. Однак книга продовжує показувати, як можна помістити reactвнутрішню петлю, використовуючи Actor.loop. В результаті ви отримуєте

loop {
    react {
        ...
    }
}

що мені здається досить схожим на

while (true) {
    receive {
        ...
    }
}

що використано раніше в книзі. І все-таки у книзі йдеться про те, що "на практиці програмам знадобиться хоча б кілька receive". То чого мені тут не вистачає? Що receiveробити цього reactне може, окрім повернення? І чому я дбаю?

Нарешті, підійшовши до основи того, що я не розумію: книга постійно згадує, як використання reactдає можливість відкинути стек викликів для повторного використання потоку. Як це працює? Чому потрібно відмовитися від стеку викликів? І чому стек виклику може бути відкинутий, коли функція припиняється, викидаючи виняток ( react), а не тоді, коли вона припиняється поверненням ( receive)?

У мене таке враження, що програмування в Scala переглянуло деякі ключові питання тут, що шкода, бо інакше це справді відмінна книга.


Відповіді:


78

По-перше, кожен актор, який чекає receive, займає нитку. Якщо він ніколи нічого не отримує, ця нитка ніколи нічого не зробить. Актор на reactне займає жодної нитки, поки щось не отримає. Щойно він отримує щось, потік йому виділяється, і він ініціалізується в ньому.

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

Існують різні причини ефективності, чому ви можете хотіти того чи іншого. Як відомо, занадто багато потоків на Java - це не дуже гарна ідея. З іншого боку, оскільки ви повинні прив’язати актора до нитки, перш ніж вона зможе react, це швидше до receiveповідомлення, ніж reactдо нього. Тож якщо у вас є актори, які отримують багато повідомлень, але роблять їх дуже мало, додаткова затримка reactможе зробити це занадто повільно для ваших цілей.


21

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

Однією з найбільш очевидних причин, чому необхідно відмовитися від стеку викликів, є те, що в іншому випадку loopметод закінчиться а StackOverflowError. Як це є, рамка досить спритно закінчується a reactкидком a SuspendActorException, яке вловлює циклічний код, який потім запускається reactзнову за допомогою andThenметоду.

Погляньте на mkBodyметод у, Actorа потім на seqметод, щоб побачити, як цикл перекладає себе - жахливо розумні речі!


20

Ці твердження про "відкидання стека" мене також на деякий час бентежили, і я думаю, що я зрозумів це зараз, і це зараз я розумію. У випадку "отримання" в блоці буде виділено виділений потік блоку (використовуючи object.wait () на моніторі), і це означає, що повний стек потоків доступний і готовий продовжувати з моменту "очікування" після отримання повідомлення. Наприклад, якщо у вас був такий код

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

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

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

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

Якщо реакція мала тип повернення недійсним, це означало б, що законно мати виписки після виклику "реагувати" (на прикладі заяви println, що друкує повідомлення "після реакції та друку 10"), але насправді це ніколи не буде виконано, оскільки лише тіло методу "реагувати" захоплюється та послідовно виконується для виконання пізніше (після надходження повідомлення). Оскільки договір реагування має тип повернення «Нічого», після реакції не може бути жодних заяв, і тому немає жодної причини підтримувати стек. У наведеному вище прикладі змінна "a" не повинна підтримуватися, оскільки заяви після викликів реакції взагалі не виконуються. Зауважте, що всі необхідні змінні організмом реагують уже зафіксовані як закриття, тому це може виконати чудово.

Рамка актора Java Kilim насправді виконує обслуговування стека, зберігаючи стек, який розкручується на реакцію, отримуючи повідомлення.


Дякую, це було дуже інформативно. Але ви не мали +aна увазі фрагменти коду, а не +10?
jqno

Чудова відповідь. Я теж не розумію цього.
santiagobasulto

8

Просто мати його тут:

Програмування на основі подій без інверсії управління

Ці роботи пов'язані зі шкалою api для "Актора" і дають теоретичну основу для акторської реалізації. Сюди входить, чому реагування ніколи не повернеться.


І другий папір. Поганий контроль над спамом ... :( [Актори, що об’єднують нитки та події] [2] [2]: lamp.epfl.ch/~phaller/doc/haller07coord.pdf "Актори, що об'єднують нитки та події"
Гексрен

0

Я не робив жодної великої роботи зі скалою / аккою, однак я розумію, що в способі планування акторів є дуже значна різниця. Акка - це просто розумна нитка, яка нарізає час виконання акторів ... Кожен раз, коли фрагмент буде виконати одне повідомлення до завершення актором на відміну від Ерланга, що може бути за інструкцією ?!

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

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