Programowanie równoległe w Erlangu, część 2

09.11.2010 - Marek Materzok
TrudnośćTrudność

Wymiana kodu w trakcie pracy

Jedną z bardziej interesujących cech Erlanga jest możliwość wymiany kodu programu w trakcie jego pracy. Mechanizm ten jest bardzo istotny z praktycznego punktu widzenia - zdolność poprawienia błędów lub wprowadzenia nowej funkcjonalności do programu bez tworzenia widocznej przerwy w jego pracy jest bardzo ważna, jeśli program ten pracuje 24 godziny na dobę i cały czas obsługuje klientów! W Erlangu osiągnięcie tego jest nietrudne.

Oto prosty program zawierający funkcję tworzącą proces i komunikującą się z nim:

-module(pingpong).
-export([mkpingpong/0,pingpong/1,ping/1]).

pingpong(X) ->
    receive
        {ping, Pid} -> Pid ! {pong, X}
    end,
    pingpong:pingpong(X).

mkpingpong() ->
    spawn(fun () -> pingpong(0) end).

ping(Pid) ->
    Pid ! {ping, self()},
    receive
        {pong, X} -> X
    end.

Nowe dla nas są linijki -module i -export. Są one związane z mechanizmem modułów w Erlangu - czyli sposobem dzielenia programu na mniejsze, niezależne fragmenty. Linijka -module wprowadza nazwę modułu, za pomocą której będziemy się do niego odwoływać: jeśli z innego modułu zechcemy wywołać funkcję zawartą w module pingpong, poprzedzimy nazwę funkcji napisem pingpong:, na przykład pingpong:mkpingpong. Linijka -export natomiast wymienia, jakie funkcje będą dostępne na zewnątrz modułu. Ponieważ możliwe jest istnienie kilku funkcji z tą samą nazwą, lecz z różną liczbą argumentów, po nazwie funkcji podajemy jej liczbę argumentów - na przykład pingpong/1.

Dziwnym może wydawać się, że rekurencyjne wywołanie w funkcji pingpong napisałem przez odwołanie się do modułu. Przecież po zastąpieniu linijki pingpong:pingpong(X) przez pingpong(X) powinniśmy dostać tak samo działający program! To prawda, ale nie do końca, co niedługo się okaże.

Zapiszmy powyższy program w pliku pingpong.erl, po czym uruchommy interpreter Erlanga erl:

$ erl
Erlang R13B01 (erts-5.7.2) [source] [rq:1] [async-threads:0] [hipe]
[kernel-poll:false]

Eshell V5.7.2  (abort with ^G)
1> 

Aby załadować właśnie napisany moduł, napiszmy w interpreterze c(pingpong):

1> c(pingpong).
{ok,pingpong}
2>

Za pomocą funkcji pingpong:mkpingpong możemy utworzyć nowy proces, a następnie wysłać do niego komunikat za pomocą pingpong:ping:

2> Pid = pingpong:mkpingpong().
<0.42.0>
3> pingpong:ping(Pid).
0
4> pingpong:ping(Pid).
0
5>

Działa! Po chwili zastanowienia jednak stwierdzamy, że chcielibyśmy, żeby kolejne wywołania funkcji ping dawały coraz większe wyniki. Moglibyśmy teraz zatrzymać interpreter, zmienić program, po czym powtórzyć wszystko od nowa, jak to robi się zazwyczaj. My jednak zrobimy inaczej. Nie wyłączając interpretera zmieńmy linijkę z wywołaniem rekurencyjnym w funkcji pingpong na:

    pingpong:pingpong(X+1).

Wróćmy do interpretera. Załadujmy moduł po raz kolejny:

5> c(pingpong).
{ok,pingpong}
6>

Ponieważ nie wyłączaliśmy interpretera, proces o identyfikatorze Pid powinien wciąż pracować. Zobaczmy, co się stanie, jak użyjemy funkcji ping:

6> pingpong:ping(Pid).
0
7> pingpong:ping(Pid).
0
8> pingpong:ping(Pid).
1
9> pingpong:ping(Pid).
2
10>

A-ha! Zaczęliśmy dostawać coraz większe wyniki - czyli wcześniej uruchomiony przez nas proces zaczął wykonywać nowy kod! W którym momencie to nastąpiło? Naturalnie - w momencie rekurencyjnego wywołania w funkcji pingpong:

Ilustracja

Podanie nazwy modułu przy tym wywołaniu było kluczowe - bez niego wymiana kodu nie mogłaby nastąpić!

0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com