+ - 0:00:00
Notes for current slide
Notes for next slide

Erlang

Języki i Paradygmaty Programowania

13.06.2016

1 / 36

Kontekst - urządzenia telekomunikacyjne

  • wysoka współbieżność (10^5-10^6 jednoczesnych akcji)
  • czas rzeczywisty (soft real-time)
  • rozproszenie
  • wysoka dostępność (9 dziewiątek, downtime<4min./rok)
  • miliony linii kodu
  • ciągła operacja
  • ciągła ewolucja
  • aktualizacje bez wyłączeń
2 / 36

Erlang

  • bardzo lekkie i tanie procesy
  • szybkie przekazywanie komunikatów
  • pełna izolacja procesów
  • automatyczna serializacja
  • programowanie funkcyjne
  • typowanie dynamiczne
  • szybki kod sekwencyjny
  • połączenie programowania sekwencyjnego i współbieżnego
3 / 36

Functions + Messages + Concurrency = Erlang

  • programowanie funkcyjne
  • przekazywanie komunikatów
  • programowanie współbieżne
  • fault-tolerance
  • wpływy Prologu (początkowo współbieżne rozszerzenie Prologu)
  • multicore
4 / 36

Programowanie funkcyjne

Haskell

qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y<x]
++ [x]
++ qsort [y | y <- xs, y>=x]

Erlang

qsort([]) -> [];
qsort([X|Xs]) -> qsort([Y || Y <- Xs, Y<X])
++ [X]
++ qsort([Y || Y <- Xs, Y>=X]).
5 / 36

Multicore

☞ Szybkość procesorów praktycznie przestała rosnąć ok 2006

Rośnie liczba rdzeni

Program sekwencyjny będzie z roku na rok działał coraz wolniej

Program współbieżny będzie z roku na rok działał coraz szybciej

6 / 36

Równoległość w Haskellu

ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N1
real 0m2.646s
user 0m2.610s
sys 0m0.044s
ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N2
real 0m1.532s
user 0m2.971s
sys 0m0.088s
ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N4
real 0m0.900s
user 0m3.396s
sys 0m0.177s
ben@azor$ time ./sudoku-par3 sudoku17.1000.txt +RTS -N8
real 0m0.598s
user 0m4.316s
sys 0m0.357s
solve :: String -> Maybe Grid
main = do
[f] <- getArgs
grids <- fmap lines $ readFile f
print (length (filter isJust (runPar $ parMap solve grids)))
7 / 36

8 / 36

Odporność na awarie (fault tolerance)

Dla zbudowania systemu odpornego na awarie potrzeba co najmniej dwóch komputerów

W razie awarii jednego, drugi przejmie jego funkcje

Konsekwencje:

  • nie ma dzielonej pamięci
  • programowanie rozproszone
  • czyste przekazywanie komunikatów
9 / 36

Równoległość - Haskell vs Erlang

Procesy w Haskellu współdzielą pamięć

W Erlangu każdy proces ma własny heap:

  • komunikaty są kopiowane
  • nie ma globalnego odśmiecania - każdy proces odśmieca swój własny heap

W Smalltalku przekazywanie komunikatów, ale przy współdzielonej pamięci

10 / 36

Wysoka odporność na awarie

Dla zbudowania systemu odpornego na awarie potrzeba co najmniej dwóch komputerów

Dla zbudowania systemu wysoce odpornego na awarie potrzeba wielu komputerów

= Skalowalność

11 / 36

Teza Armstronga

Odporność

Rozproszenie

Współbieżność

Skalowalność

są nierozłączne

12 / 36

Dwa modele współbieżności

Dzielona pamięć

  • semafory
  • wątki

Przekazywanie komunikatów

  • procesy
  • komunikaty
13 / 36

Przekazywanie komunikatów

Nie ma dzielonych zasobów

Czyste przekazywanie komunikatów

Nie ma semaforów

Obliczenia rozproszone (odporność, skalowalność)

Programowanie funkcyjne (nie ma efektów ubocznych)

14 / 36

Concurrency Oriented Programming

Wiele procesów

Pełna izolacja między procesami

Niezależne od lokalizacji

  • Możemy przenieść proces na inną maszynę

Nie ma współdzielonych danych

Tylko przesyłanie komunikatów

COP jest w pewnym sensie naturalnym rozwinięciem OOP

'The big idea in Smalltalk is messaging' - Alan Kay

15 / 36

Dlaczego COP?

Bliskie intuicyjnemu rozumieniu współbieżności

Świat realny jest współbieżny

Świat realny jest rozproszony

Świat realny jest poznawalny ;)

Tworzenie realnych aplikacji jest oparte na obserwacji rzeczywistych wzorców współbieżności i kanałów komunikacji

Ułatwia budowanie skalowalnych, rozproszonych programów.

16 / 36

Erlang w 11 minut

Niewiele o składni (czerpie z Prologu)

Programowanie sekwencyjne

Programowanie współbiezne

Programowanie rozproszone

Odporność na błędy

17 / 36

Podstawowe typy danych

  • Liczby: 1, 2, 3.1415
  • Atomy: hello
  • Napisy: "hello"
  • Ciągi bitów
    11> <<16#48,101,$l,"lo">>.
    <<"Hello">>
    13> <<2#1010>>.
    <<"\n">>
    14> <<2#1010:4>>.
    <<10:4>>
  • Rekordy: {color, "red"}
  • Listy: [red,green,blue]
    16> [1|[2|[]]].
    [1,2]
18 / 36

Zmienne

16> X = 1.
1
17> X=X+1.
* exception error: no match of right hand side value 2
18> X = 2.
* exception error: no match of right hand side value 2
19> {Y,Z} = {1,2}.
{1,2}
20> {V1,2} = {1,V2}.
1: variable 'V2' is unbound

NB Prolog:

?- X=1, X=2.
false.
?- .(V1,2) = .(1,V2).
V1 = 1,
V2 = 2.
19 / 36

Listy

21> L = [1,2,3].
[1,2,3]
22> [0,1,2,3] = [0|L].
[0,1,2,3]
23> [H|T] = L.
[1,2,3]
24> H.
1
25> T.
[2,3]

Funkcje

27> Increment = fun(X) -> X + 1 end,
27> [2,3,4] = lists:map(Increment,L).
[2,3,4]
20 / 36

Silnia

-module(math).
-export([fac/1]).
fac(0) -> 1;
fac(X) when X > 0 -> X*fac(X-1).
1> c(math).
{ok,math}
2> math:fac(5).
120
3> math:fac(-1).
* exception error: no function clause matching math:fac(-1)
(math.erl, line 4)
21 / 36

Geometry

-module(geometry).
-export([area/1]).
area({rectangle, W,H}) -> W*H;
area({square, X}) -> X*X;
area({circle, R}) -> 3.14159*R*R.
Eshell V6.4 (abort with ^G)
1> c(geometry).
{ok,geometry}
2> geometry:area({square, 10}).
100
3> geometry:area({circle, 10}).
314.159
4> geometry:area(7).
* exception error: no function clause matching geometry:area(7) (geometry.erl, line 4)
22 / 36

Obsługa błędów

area2({rectangle, W,H}) -> W*H;
area2({square, X}) -> X*X;
area2({circle, R}) -> 3.14159*R*R;
area2(Other) -> error.
47> self().
<0.11254.0>
48> geometry:area2(7).
error
49> self().
<0.11254.0>
50> geometry:area(7).
* exception error: no function clause matching geometry:area(7) (geometry.erl, line 4)
51> self().
<0.11259.0>

NB: wyjątek powoduje zakończenie procesu interpretera, nadzorca uruchamia nowy proces.

23 / 36

Drzewa BST

lookup(Key, {Key, Val,_,_}) -> {ok, Val};
lookup(Key, {Key1,Val,S,B}) when Key < Key1 ->
lookup(Key, S);
lookup(Key, {Key1, Val, S, B})->
lookup(Key, B);
lookup(key, nil) ->
not_found.
24 / 36

Komunikaty

Komunkacja między procesami jest asynchroniczna

Każdy proces ma swoją 'skrzynkę pocztową' (mailbox)

wysłanie komunikatu (do skrzynki): proces ! komunikat

Odbiór komunikatu

receive
wzorzec -> wyrażenie;
wzorzec -> wyrażenie;
...
end

Wyczyszczenie skrzynki: flush() - w shellu dodatkowo wypisuje komunikaty.

25 / 36

Komunikaty - przykład

1> self() ! hello.
hello
2> self() ! testing.
testing
3> self() ! [1,2,3].
[1,2,3]
4> flush().
Shell got hello
Shell got testing
Shell got [1,2,3]
ok
5> flush().
ok
1> receive
1> ok -> cool
1> after
1> 1000 -> timeout
1> end.
timeout
2> self() ! ok.
ok
3> receive ok -> cool after 100 -> timeout end.
cool
26 / 36

Procesy

8> Shell = self().
<0.36.0>
9> SendHello = fun()-> Shell ! hello end.
#Fun<erl_eval.20.90072148>
10> spawn(SendHello).
<0.51.0>
11> spawn(SendHello).
<0.53.0>
12> spawn(SendHello).
<0.55.0>
13> flush().
Shell got hello
Shell got hello
Shell got hello
ok
27 / 36

Współbieżność

1> Send = fun(From)->fun()->From ! { ok, {sent, From}, {rcvd, self()} } end end.
#Fun<erl_eval.6.90072148>
2> spawn(Send(self())).
<0.35.0>
3> spawn(Send(self())).
<0.37.0>
4> spawn(Send(self())).
<0.39.0>
5> flush().
Shell got {ok,{sent,<0.32.0>},{rcvd,<0.35.0>}}
Shell got {ok,{sent,<0.32.0>},{rcvd,<0.37.0>}}
Shell got {ok,{sent,<0.32.0>},{rcvd,<0.39.0>}}
ok
6> spawn(Send(self())).
<0.42.0>
7> receive {ok, {sent, From}, {rcvd, By}} -> {ok, From, By} end.
{ok,<0.32.0>,<0.42.0>}
8> flush().
ok
28 / 36

Rozproszony Erlang

-module(dist).
-export([t/1]).
t(From) -> From ! { ok, node(), self() }.
$ erl -sname bar
(bar@marbook)1> node().
bar@marbook
(bar@marbook)2> c(dist).
{ok,dist}
$ erl -sname foo
(foo@marbook)1> c(dist).
{ok,dist}
(foo@marbook)2> spawn('bar@marbook',dist,t,[self()]).
<9728.49.0>
(foo@marbook)3> flush().
Shell got {ok,bar@marbook,<9728.49.0>}
ok
29 / 36

Code hotswapping

Erlang pozwala na zmianę kodu w trakcie działania programu.

Po skompilowaniu modułu, kolejne wywołanie użyje nowej wersji

30 / 36

Procesy są niezależne

21> Crash=fun()->timer:sleep(1000),1/0 end.
#Fun<erl_eval.20.90072148>
22> self().
<0.46.0>
23> Crash().
* exception error: an error occurred when evaluating an arithmetic expression
in operator '/'/2
called as 1 / 0
24> self().
<0.65.0>
25> spawn(Crash).
<0.68.0>
=ERROR REPORT==== 12-Jun-2016::07:00:42 ===
Error in process <0.68.0> with exit value: {badarith,[{erlang,'/',[1,0],[]}]}
26> self().
<0.65.0>

Błąd w procesie nie wpływa na interpreter.

31 / 36

Wiązanie procesów

Możemy powiązać procesy tak, że awaria jednego z procesów powoduje zakończenie drugiego:

28> self().
<0.65.0>
29> spawn_link(fun() -> receive after 200 -> exit(normal) end end).
<0.80.0>
30> self().
<0.65.0>
31> spawn_link(fun() -> receive after 200 -> exit(error) end end).
<0.83.0>
* exception error: error
32> self().
<0.85.0>
32 / 36

trap_exit

Po ustawieniu flagi trap_exit, zakończenie powiązanego procesu powoduje wysłanie komunikatu, co pozwala na zrestartowanie go.

14> process_flag(trap_exit,true).
false
15> process_flag(trap_exit,true).
true
16> flush().
ok
17> spawn_link(fun() -> receive after 200 -> exit(error) end end).
<0.57.0>
18>
18> flush().
Shell got {'EXIT',<0.57.0>,error}
ok
19> spawn_link(fun() -> receive after 200 -> exit(normal) end end).
<0.60.0>
20> flush().
Shell got {'EXIT',<0.60.0>,normal}
ok
33 / 36

Monitorowanie

1> spawn_link(fun() -> receive after 200 -> exit(normal) end end).
<0.34.0>
2> spawn_link(fun() -> receive after 200 -> exit(error) end end).
<0.36.0>
* exception error: error
3> P = spawn(fun() -> receive after 10000 -> ok end end).
<0.39.0>
4> monitor(process, P).
#Ref<0.0.0.44>
5> monitor(process, P).
#Ref<0.0.0.49>
6> monitor(process, P).
#Ref<0.0.0.54>
7> flush().
Shell got {'DOWN',#Ref<0.0.0.44>,process,<0.39.0>,normal}
Shell got {'DOWN',#Ref<0.0.0.49>,process,<0.39.0>,noproc}
Shell got {'DOWN',#Ref<0.0.0.54>,process,<0.39.0>,noproc}
ok
34 / 36
8> P = spawn(fun() -> receive after 10000 -> exit(error) end end).
* exception error: no match of right hand side value <0.45.0>
9> Q = spawn(fun() -> receive after 10000 -> exit(error) end end).
<0.48.0>
10> monitor(process, Q).
#Ref<0.0.0.78>
11> monitor(process, Q).
#Ref<0.0.0.83>
12> monitor(process, Q).
#Ref<0.0.0.88>
13> flush().
Shell got {'DOWN',#Ref<0.0.0.78>,process,<0.48.0>,error}
Shell got {'DOWN',#Ref<0.0.0.88>,process,<0.48.0>,error}
Shell got {'DOWN',#Ref<0.0.0.83>,process,<0.48.0>,error}
ok
35 / 36

Open Telecom Platform

erlang:monitor/2 jest częścią biblioteki OTP

Biblioteka ta dostarcza funkcji obsługujących:

  • współbieżność
  • obliczenia rozproszone
  • monitorowanie procesów
  • protokoły komunikacyjne
  • tworzenie serwerów
  • ...

Przykłady zastosowań:

  • RabbitMQ
  • CouchDB
  • Riak
36 / 36

Kontekst - urządzenia telekomunikacyjne

  • wysoka współbieżność (10^5-10^6 jednoczesnych akcji)
  • czas rzeczywisty (soft real-time)
  • rozproszenie
  • wysoka dostępność (9 dziewiątek, downtime<4min./rok)
  • miliony linii kodu
  • ciągła operacja
  • ciągła ewolucja
  • aktualizacje bez wyłączeń
2 / 36
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow