· mchrost · 4 min

Programowanie reaktywne w pięć minut - dla świeżaków

W ostatnim czasie jednym z tzw. buzzwordów w programowaniu jest reaktywność. Jak najprościej i najkrócej opisać czym on właściwie jest? Myślę, że najlepiej będzie zacząć od tego czego sami nie lubimy, a z czym musimy się zmagać – od czekania ☺.

Życie jest czekaniem

W zwykłym życiu jesteśmy często zmuszeni oczekiwać, aż coś będziemy w stanie zrobić – czekamy na przystanku, aż przyjedzie autobus, czekamy w kolejce do kasy w markecie, czekamy u lekarza, w urzędzie… Nie lubimy tego procesu, gdyż nudzimy się (teraz w epoce smartfonów jest już z tym lepiej ☺) oraz czujemy, że tracimy czas, podczas którego moglibyśmy zrobić inne pożyteczne rzeczy (a tego żaden smartfon nie przeskoczy).

Bardzo podobnie jest w świecie programistycznym. Tak naprawdę program wykonując się na naszej maszynie przeprowadza dwa rodzaje operacji:

  • blokujące (odczyt / zapis danych na dysk, po sieci, oczekiwanie na dane od użytkownika)
  • nieblokujące (przetwarzanie danych w procesorze)

Co gorsza zwykle cykl pracy wygląda tak:

  • operacja blokująca
  • operacja nieblokująca
  • operacja blokująca
  • operacja nieblokująca

Finalnie wychodzi na to, że nasz program spędza większość swojego życia na tym, że na coś czeka - a czasu efektywnej pracy jest tyle, co kot napłakał. Programowanie wielowątkowe nieco ratuje tę sytuację (możemy czekanie zrównoleglić), ale dalej w kontekście pojedynczego wątku sytuacja wygląda kiepsko.

Dyżurny, do mnie!

Ktoś może sobie zadać pytanie – a gdyby tak zastąpić stanie w tych nieszczęsnych kolejkach podejściem „dyżurowym” (ostatnia skądinąd bardzo modnym w pracy programisty?Czyli zamiast siedzieć bezczynnie w poczekalni, jesteśmy „pod telefonem” – i gdy nadejdzie pora naszej wizyty błyskawicznie przyjeżdżamy, załatwiamy co trzeba i wracamy do domu?

I tak można w największym skrócie zdefiniować reaktywność – program jest zapisany prawie wyłącznie w postaci działań (czyli reakcji) jakie trzeba wykonać, gdy dane potrzebne do takiego działania są już gotowe. Co w efekcie pozwala nam użyć mniejszej liczby wątków, a wątki te wyżyłować maksymalnie jak się da.

Oczywiście w praktyce sprawa jest bardziej złożona, bo w tej układance brakuje kilku kolejnych elementów:

  • Po pierwsze, ktoś musi czuwać nad tym kiedy i do kogo dzwonić – i tu niezbędny jest jakiś nadzorca, który będzie się tym zajmował – czyli reaktywny framework. Jest ich kilka do wyboru, w zależności od technologii/języka, w którym pracujemy (w Javie jest to np. RxJava lub Reactor).
  • Po drugie, aby uprościć zapis oczekiwania na zdarzenia jakie chcemy obsłużyć, wprowadzamy pojęcie strumieni zdarzeń. Subskrybując się do takiego strumienia, mówimy, że chcemy obsłużyć zdarzenia jakie się w nim pojawią.
  • Po trzecie, żeby wszystko to działało, nasze operacje jakie wykonujemy, gdy zostaniemy „wezwani na dyżur” muszą być nieblokujące. Aby to zapewnić frameworki reaktywne zwykle dostarczają nam bogaty zestaw operatorów pozwalających transformować jeden strumień w drugi z zastosowaniem naszej logiki biznesowej.
  • Po czwarte, musi istnieć mechanizm zwany „backpressure”, który zapewni nam, że nie otrzymamy więcej zdarzeń do obsługi, niż jesteśmy w stanie aktualnie przetworzyć

Gdzie tu jest haczyk ?

Skoro wiemy już na czym zasadniczo polega reaktywność, to od razu narzucają się kolejne pytania:

Dlaczego dopiero teraz ktoś na to wpadł?

Absolutnie nie jest to prawdą ☺. Reaktywność istnieje już od wielu lat, tylko nikt jej tak wprost nie nazwał (co zresztą nie jest pierwszym takim przypadkiem we wszechświecie).

Chyba najbardziej sztandarowym przykładem jej użycia jest arkusz kalkulacyjny (MS Excel lub jego odpowiedniki). Zauważmy, że jakiekolwiek przeliczenia w arkuszu wykonują się wtedy i tylko wtedy gdy zmienimy wartość w jakimś polu i propagują się do wszystkich pól zależnych od zmienionego pola. Toż to czysta reaktywność!

Skoro to jest takie wydajne / wspaniałe / super (niepotrzebne skreślić) to dlaczego jest to relatywnie jeszcze niszowy kierunek w programowaniu?

Cóż - jest jeden drobny kłopot… Reaktywność działa tylko wtedy, gdy wszystkie warstwy w programie (sieć, baza etc.) są też reaktywne. Jeśli tak nie jest – jesteśmy zmuszeni tworzyć „protezy”, które symulują reaktywność danego blokującego kawałka – i mocno obniżają wydajność całości.

W przypadku sieci nie jest to obecnie już kłopot (mamy już dobrze rozpracowane nieblokujące sockety). Tak samo jest z większością niereleacyjnych baz danych. Sęk w tym, że największy problem mamy z bazami relacyjnymi – a tak się jednak składa, że prawie każdy system ich używa. Trwają prace nad poprawieniem tego, ale zagadnienie jest mocno nietrywialne.

Czy w takim razie reaktywność ma w ogóle sens ? A jeśli tak to kiedy?

Tak - jak najbardziej. Aczkolwiek najbardziej sprawdza się gdy spełnione jest kilka warunków:

  • Po pierwsze, aplikacja musi przetwarzać naprawdę duże ilości danych.
  • Po drugie, dane muszą docierać asynchronicznie.
  • Po trzecie, dane docierają z różnych źródeł, a program musi powiązać je ze sobą.

Dobrym przykładem takiej aplikacji jest „bot giełdowy” – czyli system, który na podstawie dynamicznie przychodzących danych na temat tego jakie akcje i w jakiej cenie są aktualnie sprzedawane / kupowane na giełdzie papierów wartościowych podejmuje decyzję co samemu sprzedać / kupić tak, aby zmaksymalizować zysk użytkownika.

Krótko podsumowując – reaktywność w programowaniu oznacza „zrób to co trzeba, wtedy i tylko wtedy, gdy trzeba”. Można powiedzieć, że jest to zasada, którą moglibyśmy kierować się także my – ludzie ☺.

Udostępnij:
Powrót do bloga