使用Erlang而不是其餘函數式編程語言的主要緣由之一就是Erlang的併發處理能力和分佈式編程。併發意味着程序能夠在同一時刻執行多個線程。舉個例子,操做系統容許你在同一時刻運行文字處理程序,電子表格程序,郵件客戶端,和打印任務。系統中的每一個處理器(CPU)有可能只處理一個線程,可是它以必定頻率交換這些線程,給咱們形成一種多個程序是在同一時刻執行的假象。在一個Erlang程序中很容易建立並行執行(parallel execution)的線程,而且運行這些這些線程互相通訊。Erlang中,每一個執行線程稱之爲進程(process)。html
(旁白:術語「進程(process)」一般用於各個執行線程不共享數據,術語‘’線程(thread)」用於當它們以某種方式共享數據。Erlang執行線程不共享數據,這就是爲何它們叫作進程的緣由)node
Erlang內置函數spawn用於建立一個新進程:spawn(Module, Exported_Function, List of Arguments)。考慮下面的模塊shell
-module(tut14). -export([start/0, say_something/2]). say_something(What, 0) -> done; say_something(What, Times) -> io:format("~p~n", [What]), say_something(What, Times - 1). start() -> spawn(tut14, say_something, [hello, 3]), spawn(tut14, say_something, [goodbye, 3]).
5> c(tut14). {ok,tut14} 6> tut14:say_something(hello, 3). hello hello hello done
如上所示,函數say_something輸出第一個參數,輸出次數由第二個參數指定。函數start啓動兩個進程,一個輸出「hello」三次,一個輸出「goodbye」三次。每一個進程都使用say_something函數。注意用spawn這種方式啓動一個進程所用到的函數,必須從該模塊導出。(即寫在模塊開頭的-export裏面)編程
9> tut14:start(). hello goodbye <0.63.0> hello goodbye hello goodbye
注意它沒有先輸出三次「hello」再輸出三次「goodbye」。相反,第一個進程輸出「hello」,第二個進程輸出「goodbye」,而後第一個進程再輸出「hello」,如此繼續。可是<0.63.0>從哪裏來?一個函數的返回值是最後一行表達式的返回值。在start中最後一個表達式是服務器
spawn(tut14, say_something, [goodbye, 3]).
spawn返回一個進程標識符(process identifier) , 或者說pid, 標明獨一無二的進程。因此<0.63.0>是上面spawn函數調用返回的pid。下一個例子展現了怎麼使用pid。cookie
同時還要注意在io:format中用~p代替~w。引用手冊的話:「~p和~w以相同的方式輸出標準語,可是若是輸出表示的項比一行長會合理的折斷成多行。它也嘗試去檢測一個可輸出的字符列表並將至以字符串的形式輸出。」併發
(譯註:這裏舉個例子(數據來源於官方),在shell中輸入:app
4> F = [{attributes,[[{id,age,1.50000},{mode,explicit},{typename,"INTEGER"}], [{id,cho},{mode,explicit},{typename,'Cho'}]]}, {typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}]. 5> io:format("~p",[F]). [{attributes,[[{id,age,1.5},{mode,explicit},{typename,"INTEGER"}], [{id,cho},{mode,explicit},{typename,'Cho'}]]}, {typename,'Person'}, {tag,{'PRIVATE',3}}, {mode,implicit}]ok 6> io:format("~w",[F]). [{attributes,[[{id,age,1.5},{mode,explicit},{typename,[73,78,84,69,71,69,82]}],[{id,cho},{mode,explicit},{typename,'Cho'}]]},{typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}]ok
)編程語言
在接下來的例子中建立了兩個進程,它們互相發送一些消息。分佈式
-module(tut15). -export([start/0, ping/2, pong/0]). ping(0, Pong_PID) -> Pong_PID ! finished, io:format("ping finished~n", []); ping(N, Pong_PID) -> Pong_PID ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_PID). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start() -> Pong_PID = spawn(tut15, pong, []), spawn(tut15, ping, [3, Pong_PID]). 1> c(tut15). {ok,tut15} 2> tut15: start(). <0.36.0> Pong received ping Ping received pong Pong received ping Ping received pong Pong received ping Ping received pong ping finished Pong finished
函數start建立了一個進程,讓咱們把它叫作「pong」:
Pong_PID = spawn(tut15, pong, [])
這個進程執行tut15:pong()。Pong_PID是pong進程的進程標識符。接着建立一個名爲「ping」的進程:
spawn(tut15, ping, [3, Pong_PID]),
這個進程執行:
tut15:ping(3, Pong_PID)
<0.36.0>是start函數的返回值。
「pong」進程如今這樣:
receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end.
receive 結構用於使進程等待另外一個進程的消息。它有下面的格式:
receive pattern1 -> actions1; pattern2 -> actions2; .... patternN actionsN end.
注意在end.前面沒有「;」
Erlang進程之間傳遞的消息簡單的被認爲是有效的erlang項(term)。也便是說,它們能夠是列表,tuple,整數,原子,pid等等。
每一個進程有它本身的消息隊列,用於接收消息。當新消息到達時會放入隊列的尾部。當一個進程執行一個receive表達式,消息隊列第一個接收到的消息(頭部)會和receive結構進行模式匹配。若是匹配成功,消息將會移出隊列而且執行模式後面指定的action
然而,若是第一個模式沒有匹配,第二個模式將會繼續,若是成功就執行它對應的action,若是沒有成功,繼續匹配第三個模式,如此繼續。若是到最後都沒有模式匹配成功,第一個消息將會保留在消息隊列,而後消息隊列的第二個消息(頭部下一個)繼續進行匹配,若是有任何一個模式匹配成功,相應的action就會執行,而後第二個消息會移出隊列(除第二個之外的消息全都保留)。若是第二個消息沒有匹配,嘗試第三個,如此繼續。直到到達消息隊列尾部。若是到達隊列尾部,進程會阻塞(中止執行)並等待一個新消息到達,而後重複上述過程。
Erlang的實現是很機智的,在每一個receive中它會盡量的最小化每一個消息的模式匹配次數。
如今回到ping pong的例子。
"Pong"等待消息。若是接收到原子finished,「pong」就會輸出「Pong finished」,而後什麼也不作,終止。若是收到一個{ping,Ping_PID}格式的消息,它會輸出"Pong received ping" 並向「ping」進程發送一個原子pong消息:
Ping_PID ! pong
注意「!」運算符是如何發送消息的。「!」的語法是:
Pid ! Message
即將消息(任何Erlang項)發送到Pid表示的進程。
在向「ping」進程發送了pong消息後,「pong」函數會調用自身,致使它從新回到receive結構等待另外一條消息。
如今讓咱們看看「ping」進程。回憶一下它是這樣開始的:
tut15:ping(3, Pong_PID)
請看函數ping/2,由於第一個參數是3(不是0)(第一個clause是 ping(0,Pong_PID),第二個clause是ping(N,Pong_PID),因此N成爲3),因此ping/2的第二個clause被執行。
第二個clause向pong進程發送一條消息:
Pong_PID ! {ping, self()},
self()返回執行self()的進程的pid,在這個是「ping」進程的pid。(回憶一下「pong」的代碼,self()的值最終會到達以前所說的receive結構中的Ping_PID變量。)
如今"Ping"等待一個來自「pong」的答覆:
receive pong -> io:format("Ping received pong~n", []) end,
當收到回覆時它會輸出"Ping received pong",在這以後ping函數也會調用本身。
ping(N - 1, Pong_PID)
N-1使得第一個參數減一,直到它變成零。 當變成零時,ping/2的第一個clause就會被執行:
ping(0, Pong_PID) -> Pong_PID ! finished, io:format("ping finished~n", []);
該函數會向pong進程發送原子finished(正如上面描述的這會使得pong結束進程),接着會輸 "ping finished"。 而後"Ping"會由於沒有事情作而終止。
在以前的例子中,「pong」進程最早被建立,並將它的進程標識符給接下來建立的「ping」進程做爲參數。也便是說,「ping」必須經過某種方式知道「pong」進程才能向它發送消息。有時獨立啓動的進程須要知道彼此的標識符。鑑於此Erlang提供了一種進程機制來給進程命名而不是在一堆函數中混亂傳遞PID參數,這種機制是經過內置函數register完成的。
register(some_atom, Pid)
如今讓咱們使用下面的代碼來重寫ping pong 例子,給「pong」進程一個名字:
-module(tut16). -export([start/0, ping/1, pong/0]). ping(0) -> pong ! finished, io:format("ping finished~n", []); ping(N) -> pong ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start() -> register(pong, spawn(tut16, pong, [])), spawn(tut16, ping, [3]).
2> c(tut16). {ok, tut16} 3> tut16:start(). <0.38.0> Pong received ping Ping received pong Pong received ping Ping received pong Pong received ping Ping received pong ping finished Pong finished
這是start/0函數,
register(pong, spawn(tut16, pong, [])),
同時作了啓動「pong」線程,給線程命名兩件事。在「ping」進程中,能夠這樣給「pong」進程發送消息:
pong ! {ping, self()},
ping/2 如今變成了ping/1,省去了Pong_PID參數(避免在各個函數中混亂傳遞Ping_PID/Pong_PID參數)
讓咱們重寫ping pong這個例子,使「ping」和「pong」在不一樣電腦上運行。第一件事是設置。Erlang的分佈式實現提供了一個很是基礎的驗證機制來避免一臺電腦不當心鏈接到Erlang分佈式集羣。Erlang集羣的交流必須有一個相同的magic cookie。要實現這個最簡單的方法是經過一個.erlang.cookie文件,將它放置於集羣中的各臺電腦(譯註:即服務器,後文也譯作「電腦(computer)」)的home目錄,這樣它們就能相互通訊:
.erlang.cookie文件包含了一行相同的原子。舉個例子,在Linux或UNIX系統shell中
$ cd $ cat > .erlang.cookie this_is_very_secret $ chmod 400 .erlang.cookie
chmod命令將只容許文件的擁有者訪問.erlang.cookie文件。這是需求不是必要。
當你啓動一個Erlang系統,想和另外一個Erlang系統通訊,你必須給它一個名字,好比:
$ erl -sname my_name
在後面咱們會討論更多關於這個的細節。若是你想實驗一下分佈式Erlang,可是你只有一臺電腦,你能夠在這臺電腦上啓動兩個獨立的Erlang系統,只須要給它們指定不一樣的名字。每一個運行着Erlang系統的電腦叫作Erlang節點(Erlang node)
(注意: erl -sname假定全部節點都是用相同的IP,若是咱們想在不一樣的IP上運行Erlang系統請使用 -name代替。可是IP地址必須給全。)
像下面同樣修改ping pong例子使之運行在不一樣的節點:
-module(tut17). -export([start_ping/1, start_pong/0, ping/2, pong/0]). ping(0, Pong_Node) -> {pong, Pong_Node} ! finished, io:format("ping finished~n", []); ping(N, Pong_Node) -> {pong, Pong_Node} ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_Node). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start_pong() -> register(pong, spawn(tut17, pong, [])). start_ping(Pong_Node) -> spawn(tut17, ping, [3, Pong_Node]).
假設這兩臺電腦叫作gollum和kosken。第一個節點是kosken,啓動ping,第二個是gollum,啓動pong。
kosken以下:
kosken> erl -sname ping Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0] Eshell V5.2.3.7 (abort with ^G) (ping@kosken)1>
這是 gollum:
gollum> erl -sname pong Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0] Eshell V5.2.3.7 (abort with ^G) (pong@gollum)1>
接着在gollum上啓動pong:
(pong@gollum)1> tut17:start_pong(). true
在kosken節點上啓動ping進程:
(ping@kosken)1> tut17:start_ping(pong@gollum). <0.37.0> Ping received pong Ping received pong Ping received pong ping finished
如上所示,ping pong都已經在運行了。在「pong」那邊:
(pong@gollum)2> Pong received ping Pong received ping Pong received ping Pong finished (pong@gollum)2>
注意tut17的代碼,你會注意到pong函數的代碼沒有改變,下面的代碼也同樣,它不關心ping進程所在的節點:
{ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong,
所以,Erlang pid包含了進程在哪執行的信息。若是你知道一個進程的pid,就能夠用「!」運算符發送消息,而不用考慮進程在不在相同的節點。
有一點不一樣是消息怎樣發送給另外一個節點上已註冊的進程:
{pong, Pong_Node} ! {ping, self()},
一個元組tuple {registered_name,node_name}用來代替 registered_name。
在錢的例子中,‘’ping」和「pong」由兩個獨立的Erlang節點的shell中啓動,也就是說spawn能夠在不一樣的節點上啓動進程。
下面的例子又是ping pong程序,可是這一次「ping」在另外一個節點啓動:
-module(tut18). -export([start/1, ping/2, pong/0]). ping(0, Pong_Node) -> {pong, Pong_Node} ! finished, io:format("ping finished~n", []); ping(N, Pong_Node) -> {pong, Pong_Node} ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_Node). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start(Ping_Node) -> register(pong, spawn(tut18, pong, [])), spawn(Ping_Node, tut18, ping, [3, node()]).
假設在kosken上被名爲ping的Erlang系統已經啓動,而後在gollum上這樣作:
(pong@gollum)1> tut18:start(ping@kosken). <3934.39.0> Pong received ping Ping received pong Pong received ping Ping received pong Pong received ping Ping received pong Pong finished ping finished
注意gollum接收全部的輸出。這是由於I/O系統會找到進程從哪啓動,而後在那輸出。
如今寫一個完整的例子,叫作「messenger」。messenger這個程序運行在不一樣的Erlang節點上登錄而後互相發送消息(message)。
在開始前,注意下面幾點:
messenger容許建立客戶端而後鏈接中央服務器,並服務器會知曉客戶端是哪些、它們在哪。也就是說,用戶不須要關係當前節點的名字和其餘節點在哪就能發送消息。
messenger.erl文件以下:
%%% Message passing utility. %%% User interface: %%% logon(Name) %%% One user at a time can log in from each Erlang node in the %%% system messenger: and choose a suitable Name. If the Name %%% is already logged in at another node or if someone else is %%% already logged in at the same node, login will be rejected %%% with a suitable error message. %%% logoff() %%% Logs off anybody at that node %%% message(ToName, Message) %%% sends Message to ToName. Error messages if the user of this %%% function is not logged on or if ToName is not logged on at %%% any node. %%% %%% One node in the network of Erlang nodes runs a server which maintains %%% data about the logged on users. The server is registered as "messenger" %%% Each node where there is a user logged on runs a client process registered %%% as "mess_client" %%% %%% Protocol between the client processes and the server %%% ---------------------------------------------------- %%% %%% To server: {ClientPid, logon, UserName} %%% Reply {messenger, stop, user_exists_at_other_node} stops the client %%% Reply {messenger, logged_on} logon was successful %%% %%% To server: {ClientPid, logoff} %%% Reply: {messenger, logged_off} %%% %%% To server: {ClientPid, logoff} %%% Reply: no reply %%% %%% To server: {ClientPid, message_to, ToName, Message} send a message %%% Reply: {messenger, stop, you_are_not_logged_on} stops the client %%% Reply: {messenger, receiver_not_found} no user with this name logged on %%% Reply: {messenger, sent} Message has been sent (but no guarantee) %%% %%% To client: {message_from, Name, Message}, %%% %%% Protocol between the "commands" and the client %%% ---------------------------------------------- %%% %%% Started: messenger:client(Server_Node, Name) %%% To client: logoff %%% To client: {message_to, ToName, Message} %%% %%% Configuration: change the server_node() function to return the %%% name of the node where the messenger server runs -module(messenger). -export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]). %%% Change the function below to return the name of the node where the %%% messenger server runs server_node() -> messenger@bill. %%% This is the server process for the "messenger" %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] server(User_List) -> receive {From, logon, Name} -> New_User_List = server_logon(From, Name, User_List), server(New_User_List); {From, logoff} -> New_User_List = server_logoff(From, User_List), server(New_User_List); {From, message_to, To, Message} -> server_transfer(From, To, Message, User_List), io:format("list is now: ~p~n", [User_List]), server(User_List) end. %%% Start the server start_server() -> register(messenger, spawn(messenger, server, [[]])). %%% Server adds a new user to the user list server_logon(From, Name, User_List) -> %% check if logged on anywhere else case lists:keymember(Name, 2, User_List) of true -> From ! {messenger, stop, user_exists_at_other_node}, %reject logon User_List; false -> From ! {messenger, logged_on}, [{From, Name} | User_List] %add user to the list end. %%% Server deletes a user from the user list server_logoff(From, User_List) -> lists:keydelete(From, 1, User_List). %%% Server transfers a message between user server_transfer(From, To, Message, User_List) -> %% check that the user is logged on and who he is case lists:keysearch(From, 1, User_List) of false -> From ! {messenger, stop, you_are_not_logged_on}; {value, {From, Name}} -> server_transfer(From, Name, To, Message, User_List) end. %%% If the user exists, send the message server_transfer(From, Name, To, Message, User_List) -> %% Find the receiver and send the message case lists:keysearch(To, 2, User_List) of false -> From ! {messenger, receiver_not_found}; {value, {ToPid, To}} -> ToPid ! {message_from, Name, Message}, From ! {messenger, sent} end. %%% User Commands logon(Name) -> case whereis(mess_client) of undefined -> register(mess_client, spawn(messenger, client, [server_node(), Name])); _ -> already_logged_on end. logoff() -> mess_client ! logoff. message(ToName, Message) -> case whereis(mess_client) of % Test if the client is running undefined -> not_logged_on; _ -> mess_client ! {message_to, ToName, Message}, ok end. %%% The client process which runs on each server node client(Server_Node, Name) -> {messenger, Server_Node} ! {self(), logon, Name}, await_result(), client(Server_Node). client(Server_Node) -> receive logoff -> {messenger, Server_Node} ! {self(), logoff}, exit(normal); {message_to, ToName, Message} -> {messenger, Server_Node} ! {self(), message_to, ToName, Message}, await_result(); {message_from, FromName, Message} -> io:format("Message from ~p: ~p~n", [FromName, Message]) end, client(Server_Node). %%% wait for a response from the server await_result() -> receive {messenger, stop, Why} -> % Stop the client io:format("~p~n", [Why]), exit(normal); {messenger, What} -> % Normal response io:format("~p~n", [What]) end.
要運行這個程序,你須要:
接下來的例子是使用這個程序,在四個不一樣電腦上啓動Erlang節點。若是你沒有那麼多電腦那麼能夠考慮在一臺機器上啓動不一樣的節點(譯註:-sname,具體能夠參見前面小結)。
四個Erlang節點分別是:messenger@super, c1@bilbo, c2@kosken, c3@gollum.
首先啓動服務器節點messenger@supe:
(messenger@super)1> messenger:start_server(). true
接着在c1@bilbo上登錄Peter:
(c1@bilbo)1> messenger:logon(peter). true logged_on
在c2@kosken上登錄James:
(c2@kosken)1> messenger:logon(james). true logged_on
Fred在c3@gollum上登錄:
(c3@gollum)1> messenger:logon(fred). true logged_on
如今Peter給Fred發送消息:
(c1@bilbo)2> messenger:message(fred, "hello").
ok
sent
Fred收到消息並回復Peter一條消息而後註銷:
Message from peter: "hello" (c3@gollum)2> messenger:message(peter, "go away, I'm busy"). ok sent (c3@gollum)3> messenger:logoff(). logoff
James如今嘗試向Fred發送消息:
(c2@kosken)2> messenger:message(fred, "peter doesn't like you").
ok
receiver_not_found
可是失敗了,由於Fred早就離線了。
讓咱們先看看這裏引進的新概念。
有兩個版本的server_transfer函數:一個有四個參數(server_transfer/4) 一個有五個參數(server_transfer/5)。Erlang將他們視做不一樣的函數。
注意怎樣寫server函數讓它調用本身,經過server(User_List)造成一個循環結構。Erlang編譯器很「聰明」,它會進行代碼優化,以致於它真的會變成一個循環而不是函數調用。可是這隻限於在這個調用後沒有其它工做。這會致使進程(譯註:的內存佔用)在每次循環後變得愈來愈大。
也使用了一些lists模塊的函數。這是一個很是有用的模塊,建議看看它的使用手冊(erl -man lists)。lists:keymember(Key,Position,Lists)遍歷tuple列表而後檢查tuple的Position位置是否和Key匹配,tuple的第一個元素是1.若是尋找成功返回true,不然返回false。
3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]). true 4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]). false
lists:keydelete的工做方式相似,只是若是找到就刪除它並返回剩餘列表:
5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
[{x,y,z},{b,b,b},{q,r,s}]
lists:keysearch相似於lists:keymember,可是返回 {value,Tuple_Found},或者尋找失敗返回false原子。
在lists模塊有不少有用的函數。
一個Erlang進程(概念上的)會一直運行直到它執行receive結構,直到遍歷消息隊列後沒有發現和receive結構中的模式相匹配的消息。之因此說是「概念上的」是由於Erlang系統執行各個進程實際上是會共享CPU時間的。
當一個進程沒有事作的時候它會終止,即它調用的最後一個函數簡單返回且再也不調用其餘函數。另外一個終止進程的方法是調用exit/1,。exit/1的參數有特別的意義,咱們將會在後面討論。在這個例子中,調用exit(normal)便可,它會進程運行到沒有事作再終止是同樣的效果。
內置函數whereis(RegisteredName)檢查一個名爲RegisteredName的具名進程是否存在。若是存在,返回它的pid,若是不存在, ,返回原子undefined。
到目前爲止你應該已經理解了messenger模塊的大部分代碼。讓咱們取一個片斷看看它的細節。
第一個用戶「發送」消息:
messenger:message(fred, "hello")
在測試了客戶端進程存在以後:
whereis(mess_client)
將會發送一條消息給mess_client:
mess_client ! {message_to, fred, "hello"}
它的實現是客戶端向服務器發送消息:
{messenger, messenger@super} ! {self(), message_to, fred, "hello"},
而後等待服務器的回覆。
把目光轉向服務器,它收到消息而後調用:
server_transfer(From, fred, "hello", User_List),
它檢查User_List中的pid:
lists:keysearch(From, 1, User_List)
若是keysearch返回原子false,引起錯誤,服務器會這樣回覆:
From ! {messenger, stop, you_are_not_logged_on}
它將被客戶端收到,而後客戶端執行exit(normal)終止。若是keysearch返回{value,{From,Name}},很明顯用戶已經登陸,他的名字(peter)會被綁定到Name上。
如今讓咱們調用:
server_transfer(From, peter, fred, "hello", User_List)
注意server_transfer/5,它不一樣於server_transfer/4。另外一個keysearch會在User_List上進行,而後返回fred客戶端的pid:
lists:keysearch(fred, 2, User_List)
此次Position指定爲2,也就是tuple的第二個元素和fred進行匹配。若是返回原子false,fred就沒有登陸而後發送下面的消息:
From ! {messenger, receiver_not_found};
客戶端會收到該條消息。
若是keysearch返回:
{value, {ToPid, fred}}
會向fred發送:
ToPid ! {message_from, peter, "hello"},
向peter發送:
From ! {messenger, sent}
Fred'收到該條消息而後輸出:
{message_from, peter, "hello"} ->
io:format("Message from ~p: ~p~n", [peter, "hello"])
Peter客戶端在await_result函數調用中接收消息。