Pozwolę sobie najpierw powiedzieć, że mam całkiem spore doświadczenie w Javie, ale dopiero niedawno zainteresowałem się językami funkcjonalnymi. Niedawno zacząłem patrzeć na Scalę, która wydaje się bardzo fajnym językiem.
Jednak czytałem o strukturze aktora Scali w programowaniu w Scali i jest jedna rzecz, której nie rozumiem. W rozdziale 30.4 jest napisane, że używanie react
zamiast receive
umożliwia ponowne użycie wątków, co jest dobre dla wydajności, ponieważ wątki są drogie w JVM.
Czy to oznacza, że jeśli pamiętam, żeby zadzwonić react
zamiast dzwonić receive
, mogę założyć tylu aktorów, ilu zechcę? Zanim odkryłem Scalę, bawiłem się z Erlangiem, a autor Programming Erlang chwali się, że bez wysiłku stworzył ponad 200 000 procesów. Nie chciałbym tego robić z wątkami Java. Na jakie ograniczenia patrzę w Scali w porównaniu z Erlangiem (i Javą)?
Jak działa ponowne użycie tego wątku w Scali? Załóżmy dla uproszczenia, że mam tylko jeden wątek. Czy wszyscy aktorzy, których zacznę uruchamiać sekwencyjnie w tym wątku, czy będzie miało miejsce przełączanie zadań? Na przykład, jeśli uruchomię dwóch aktorów, którzy wysyłają sobie wiadomości ping-pong, czy zaryzykuję zakleszczenie, jeśli zaczną się w tym samym wątku?
Według Programming in Scala pisanie aktorów do użycia react
jest trudniejsze niż z receive
. Brzmi to wiarygodnie, ponieważ react
nie wraca. Jednak książka pokazuje, jak można umieścić react
wewnątrz pętli za pomocą Actor.loop
. W rezultacie otrzymujesz
loop {
react {
...
}
}
który wydaje mi się dość podobny do
while (true) {
receive {
...
}
}
który został użyty wcześniej w książce. Mimo to książka mówi, że „w praktyce programy będą wymagały co najmniej kilku receive
”. Więc czego tu brakuje? Co może receive
zrobić, react
czego nie może poza powrotem? I dlaczego mnie to obchodzi?
Na koniec dochodzę do sedna tego, czego nie rozumiem: książka ciągle wspomina, jak używanie react
umożliwia odrzucenie stosu wywołań w celu ponownego użycia wątku. Jak to działa? Dlaczego konieczne jest odrzucenie stosu wywołań? I dlaczego można odrzucić stos wywołań, gdy funkcja kończy się przez zgłoszenie wyjątku ( react
), ale nie wtedy, gdy kończy się zwracaniem ( receive
)?
Mam wrażenie, że programowanie w Scali tuszowało tu kilka kluczowych kwestii, a szkoda, bo poza tym to naprawdę świetna książka.
źródło
Odpowiedzi:
Po pierwsze, każdy czekający aktor
receive
zajmuje wątek. Jeśli nic nie otrzyma, nic nie zrobi. Aktorreact
nie zajmuje żadnego wątku, dopóki czegoś nie otrzyma. Gdy coś otrzyma, przydzielany jest do niego wątek i jest w nim inicjowany.Teraz część inicjująca jest ważna. Oczekuje się, że wątek odbierający coś zwróci, a wątek reagujący nie. Zatem poprzedni stan stosu na końcu ostatniego
react
może być i jest całkowicie odrzucony. Brak konieczności zapisywania ani przywracania stanu stosu przyspiesza uruchamianie wątku.Istnieje wiele powodów, dla których możesz chcieć jednego lub drugiego. Jak wiesz, posiadanie zbyt wielu wątków w Javie nie jest dobrym pomysłem. Z drugiej strony, ponieważ trzeba wcześniej dołączyć aktora do wątku
react
, jest to szybsze doreceive
przesłania niżreact
do niego. Więc jeśli masz aktorów, którzy otrzymują wiele wiadomości, ale robią z nimi bardzo mało, dodatkowe opóźnieniereact
może spowodować, że będzie to zbyt wolne dla twoich celów.źródło
Odpowiedź brzmi „tak” - jeśli twoi aktorzy nie blokują niczego w twoim kodzie i używasz
react
, możesz uruchomić swój program „współbieżny” w ramach jednego wątku (spróbuj ustawić właściwość systemową,actors.maxPoolSize
aby się dowiedzieć).Jednym z bardziej oczywistych powodów, dla których konieczne jest odrzucenie stosu wywołań, jest to, że w przeciwnym razie
loop
metoda zakończyłaby się rozszerzeniemStackOverflowError
. W rzeczywistości framework dość sprytnie kończy areact
, rzucając aSuspendActorException
, który jest przechwytywany przez kod pętli, który następnie uruchamiareact
ponownie za pomocąandThen
metody.Spójrz na
mkBody
metodę in,Actor
a następnieseq
metodę, aby zobaczyć, jak pętla sama się zmienia - okropnie sprytna rzecz!źródło
Te stwierdzenia o „odrzuceniu stosu” również przez chwilę zdezorientowały mnie i myślę, że teraz rozumiem i to jest teraz moje zrozumienie. W przypadku "odbioru" istnieje dedykowane blokowanie wątków w wiadomości (za pomocą object.wait () na monitorze), co oznacza, że cały stos wątków jest dostępny i gotowy do kontynuowania od momentu "oczekiwania" na odebranie wiadomość. Na przykład, jeśli masz następujący kod
wątek czekałby w wywołaniu odbierającym, aż wiadomość zostanie odebrana, a następnie kontynuowałby i drukował komunikat „po odebraniu i wydrukowaniu 10” z wartością „10”, która znajduje się w ramce stosu przed zablokowaniem wątku.
W przypadku, gdy nie ma takiego dedykowanego wątku, cała treść metody reago jest przechwytywana jako zamknięcie i jest wykonywana przez dowolny dowolny wątek na odpowiednim aktorze otrzymującym wiadomość. Oznacza to, że zostaną wykonane tylko te instrukcje, które mogą być przechwycone jako samo zamknięcie, i wtedy pojawia się zwracany typ „Nic”. Rozważmy następujący kod
Gdyby reakcja miała zwracany typ void, oznaczałoby to, że dozwolone jest posiadanie instrukcji po wywołaniu „reakcja” (w przykładzie instrukcja println, która drukuje wiadomość „po zareaguj i wypisz 10”), ale w rzeczywistości nigdy nie zostałby wykonany, ponieważ tylko treść metody „reaguj” jest przechwytywana i sekwencjonowana do późniejszego wykonania (po nadejściu wiadomości). Ponieważ kontrakt reaguje na zwracany typ „Nothing”, nie może być żadnych instrukcji po reakcji, a nie ma powodu, aby utrzymywać stos. W powyższym przykładzie zmienna „a” nie musiałaby być utrzymywana, ponieważ instrukcje po wywołaniach reakcji nie są w ogóle wykonywane. Zauważ, że wszystkie potrzebne zmienne przez ciało reagują już są przechwycone jako zamknięcie, więc może działać dobrze.
Platforma aktora java Kilim w rzeczywistości zajmuje się obsługą stosu, zapisując stos, który jest rozwijany w odpowiedzi na otrzymanie wiadomości.
źródło
+a
fragmentów kodu zamiast+10
?Wystarczy mieć to tutaj:
Programowanie oparte na zdarzeniach bez odwrócenia sterowania
Dokumenty te są powiązane ze scala api dla aktora i zapewniają ramy teoretyczne dla implementacji aktora. Obejmuje to, dlaczego reakcja może nigdy nie powrócić.
źródło
Nie wykonałem żadnej większej pracy ze scala / akka, ale rozumiem, że istnieje bardzo znacząca różnica w sposobie planowania aktorów. Akka to po prostu sprytna pula wątków, która dzieli wykonanie aktorów w czasie ... Każdy wycinek czasu będzie wykonywał jedną wiadomość do zakończenia przez aktora, inaczej niż w Erlangu, który mógłby być wykonywany na instrukcję ?!
To prowadzi mnie do wniosku, że reakcja jest lepsza, ponieważ sugeruje bieżącemu wątkowi, aby wziął pod uwagę innych aktorów do planowania, w których jako odbieranie „może” zaangażować bieżący wątek do kontynuowania wykonywania innych komunikatów dla tego samego aktora.
źródło