Axum – Agenci w akcji

Powakacyjna kontynuacja serii na temat różnych podejść w pisaniu aplikacji równoległych. Po koniec lipca przedstawiłem model Agenta, a dzisiaj chciałbym przedstawić jego Microsoftową implementację.

Axum to kolejny inkubacyjny projekt z Redmond, tak więc naprawdę nie wiadomo, co się z nim dalej stanie. Model agenta [1], który przedstawiłem w poprzednim poście, został przez giganta potraktowany całkiem poważnie. Solidna implementacja, skalowalność, wtyczka do Visual Studio – to wszystko daje całkiem duże pole do popisu, ale powoli…

Axum to jezyk programowania – pewnie się zdziwiliście – w 99% oparty na C# i w pełni współpracujący z platformą .NET. To narzędzie umożliwiające koordynację niezależnych komponentów aplikacji, definiujące agentów oraz ich interakcje. Jest to język „special-purpose”, więc z miejsca wprowadzona jest tu równoległość.

Najprościej przedstawić Axum budując bardzo prostą aplikację taką jak  kalkulator. Na początek zatem musimy zaplanować, jakie elementy będą nam potrzebne – myśląc naturalnie w świecie agentów. Otóż, potrzebny będzie nam agent wykonujący pojedyncze działanie, np. dodawanie, ale również agent, który przekaże mu dane jednocześnie odbierając je od nas (np. z konsoli), tj. agent utożsamiany z punktem wejściowym aplikacji. Agent powinien również definiować kanał, czyli określenie rodzaju wiadomości, jaką może obsłużyć.

Zacznijmy zatem od kanału. Nazwijmy go MathChannel:

  1. channel MathChannel
  2. {
  3.    input int X;
  4.    input int Y;
  5.    output int Z;
  6. }

Jak widać, kanał definiuje pewne pola zwane portami. Oprócz typu decyduje się również o kierunku. Porty to nic innego jak bufory. Kolejność używania portów przez agenta można zdefiniować w tzw. patternach, ale na tym etapie poznawania Axum nie będą one potrzebne.

Stwórzmy zatem agenta dodawania:

  1. public agent Adder : channel MathChannel
  2. {
  3.    public Adder()
  4.    {
  5.       while(true)
  6.       {
  7.          Z <– ( receive(X) + receive(Y) );
  8.       }
  9.    }
  10. }

Należy zauważyć, że definiując agenta, musimy zdecydować, jaki kanał będzie obsługiwał, a w tym wypadku oczywiście będzie to nasz kanał matematyczny. Jak widzicie, kanał nie decyduje o funkcjonalności, a jedynie o możliwym interfejsie obsługi. Konstruktor tego agenta jest punktem wejściowym. Czytanie z kanału jest blokujące, tak więc łatwo zauważyć, na czym polega działanie agenta: czeka on na parametr, który pojawi się na porcie X w kanale. Jeśli przyjdzie, czeka na Y, a jeśli i ten przyjdzie – na port Z wstawia wynik działania i znów nasłuchuje na porcie X. O ile konstrukcja pobierania z portu jest „normalna”, to wysyłanie wyniku na port operatorem „<–” może troszkę dziwić. Axum definiuje więcej podobnych operatorów.

Czas na kluczowego agenta:

  1. public agent App : channel Application
  2. {
  3.    public App()
  4.    {
  5.       var a = Adder.CreateInNewDomain();
  6.       while(true)
  7.       {
  8.          Console.Write("X: ");
  9.          int x = Int32.Parse(Console.ReadLine());
  10.          a::X <– x;
  11.  
  12.          Console.Write("X: ");
  13.          int y = Int32.Parse(Console.ReadLine());
  14.          a::Y <– y;
  15.  
  16.          int za = receive(a::Z);
  17.          Console.WriteLine("Adder Z: " + za);
  18.       }
  19.    }
  20. }

Agent będący punktem wejściowym aplikacji musi implementować kanał Application, co w tym wypadku polega jedynie na deklaracji. W konstruktorze (czyli w ciele wykonywania agenta) tworzymy pozostałych potrzebnych nam agentów, w tym wypadku tylko jednego – do dodawania. Zauważcie, że stworzony został w innej domenie niż obecny agent, przez co będzie mógł działać równolegle. Następnie najzwyczajniej w świecie pobieramy liczbę z konsoli i wysyłamy ją na dany port agenta – port który definiuje zgodnie z kanałem. To samo robimy z drugim argumentem. Znaną funkcją receive odbieramy wynik. Ciekawy jest sposób dostępu do portów, a mianowicie nie przez kropkę, jak przywykliśmy do tego typu konstrukcji, a przez podwójny dwukropek.

Myślę, że kod jest na tyle prosty, że każdy zrozumiał ideę, ale teraz więcej szczegółów.

  • każdy agent to oddzielny wątek
  • wysyłanie na porty odbywa się przez kopiowanie i jest nieblokujące
  • porty mogą być jedynie typów niezmiennych (immutable) lub w pełni serializowanych
  • możliwe są operacje Multiplex, Combine, Broadcast, Alternate
  • kanały są kompatybilne z WCF

Axum to naprawdę potężne narzędzie, które bez problemu można wpleść w istniejące projekty .NETowe. Jest skalowane, intuicyjne i proste w zaadaptowaniu. W powyższym wpisie przedstawiłem jedynie  zarys funkcjonalności, ale wszystkich gorąco zachęcam do przyjrzeniu się temu narzędziu bliżej.

Dostępna jest wersja Axum zarówno do VisualStudio 2008 oraz 2010 [2]. Dla ciekawskich powyższy projekt powiększony o jeszcze jednego agenta prezentuje na swoim googlowym hostingu kodu [3]. Zapraszam!

Źródła:

Promuj

Agent z wiadomością

Z implementacją równoległych zadań można sobie poradzić na wiele różnych sposobów. Zazwyczaj jednak instalujemy blokady, monitory, tworzymy transakcje, oplatamy synchroniczny kod. Człowiek myśli synchronicznie, działa synchronicznie i często pisze – jeśli potrafi – synchroniczny kod. Co by się jednak stało, gdyby całe takie podejście odwrócić do góry nogami? Takich przełomowych projektów było wiele, oferowały nowy model pisania równoległych aplikacji, bez pamięci wspólnej i z wymianą komunikatów. Takie rozwiązanie, mimo szeregu ograniczeń, jest bardzo skalowalne. Przejdźmy jednak do rzeczy. W dzisiejszym wpisie chciałbym przedstawić Wam Model Agenta [1] (czasami zwany również – Modelem Aktora).

Cała idea tego podejścia polega na wyodrębnieniu agentów/aktorów, którzy spełniają jakąś logicznie pojedynczą rolę. Agenci nie dzielą między sobą żadnych zasobów, chyba że umiejscowieni są w jednej domenie. Zasoby takiej domeny podlegają wtedy synchronizacji. Komunikacja między agentami odbywa się poprzez wiadomości, które są przekazywane – i tu uwaga – przez kopiowanie. W ten prosty sposób każda domena może działać równoległe względem innej – często bywa tak, że w domenie jest tylko jeden agent.

Aby zastosować Model Agenta, najlepiej jest wyobrazić sobie projektowaną aplikację jako – uwaga – osiedle. Cała aplikacja to jedno, przedmiejskie osiedle z domkami i pocztą – tyle. W każdym domku mieszka przynajmniej jeden agent/agentka – dom to właśnie domena. W domku z każdego sprzętu (zasobu) może korzystać tylko jeden agent – mieszkający w tym domku (sąsiad nie może niczego dotykać). Mamy więc synchronizację wewnątrz domeny. Agenci komunikują się ze sobą albo przez listy, które wkładają do skrzynki przed domkiem, a listonosz, zanosi je do odpowiedniej skrzynki adresata (listonosz jest w tym przypadku kanałem), albo przez karteczki na lodówce (wspólną pamięć), kiedy komunikujemy się z domownikiem (agentem w tym samym domu). Każdy agent jest inżynierem w danej kategorii. W listach dostaje dane do pracy i w listach również odsyła wyniki. Jeśli dwóch agentów w domku potrzebuje wiertarki, to jeden musi poczekać, aż drugi skończy swoją pracę. Dokładnie mówiąc, każdy domek działa w innym wątku, a osiedle utożsamiane jest z jednym procesem. Oczywiście, nic nie stoi na przeszkodzie komunikacji między innymi osiedlami :-)

Wiem że, jest to dość duża abstrakcja, ale na sam początek przygody z agentami powinna wystarczyć. Jest tam też kilka niedociągnięć, ale myślę, że kiedy będę omawiał implementacje, na wszystkie pytania, znajdzie się odpowiedź.

Każda implementacja posiada własne smaczki, które wprowadzają pewne udogodnienia w platformie/języku, na jakie dane rozwiązanie jest kierowane. Choć na rynku istnieje kilka implementacji kierowanych na platformę .NET, to w kolejnych wpisach, chciałbym się skoncentrować jedynie na rozwiązaniu zaproponowanym przez Microsoft – Axum [2]. Zapraszam wkrótce.

Źródła:

Promuj