erlang-并发编程

进程

spawn函数创建进程
spawn(Module, Exported_Function, List of Arguments)
模块

-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]).

输出结果

9> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye

函数的返回值是最后一个表达式的值,spawn返回的是进程标识符pid
<0.63.0>就是pid

消息传递

-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]).

receive用来接收其他进程发送来的消息

receive
   pattern1 ->
       actions1;
   pattern2 ->
       actions2;
   ....
   patternN
       actionsN
end.

进程之间的消息,可以是任何erlang项,比如:列表、元组、整数、原子、进程标识
每个进程有独立的接收消息队列,新接收的消息被放置在接收队列的尾部
执行receive时,消息的第一个消息与receive后第一个匹配,则消息删除,执行代码,否则继续
如果都不匹配,第一个消息留在队列,然后处理第二个消息
如果所有消息都处理结束
Pid ! Message表示,任何数据都发送到Pid进程的消息队列中
self()函数返回当前进程的pid

分布式编程

分布式 Erlang 系统实现基本的安全机制,阻止未授权的设备访问本机erlang系统
同一系统的Erlang通信需要设置相同的的magic cookie
最方便的方式,在所有计算机home下创建.erlang.cookie文件
文件中只有一行原子值

$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie

.erlang.cookie只有拥有这能访问
erlang互相通信时,需要设置本机的名称

$erl -sname my_name

如果在同一台机器上启动两个erlang系统,可以赋予不同的名称,每个erlang都是节点的node
如果我们的 Erlang 结点位于不同的 IP 域中,则我们需要使用 -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

pong的函数没有任何改变,无论在哪个节点下,下面的代码都能正确工作
pid中包含运行在哪个节点的信息

{ping, Ping_PID} ->
    io:format("Pong received ping~n", []),
    Ping_PID ! pong,

无论运行在哪个节点上,!符号都能讲信息发送到本机的进程队列中
如果想通过进程注册的名称,向其他节点发送消息
{pong, Pong_Node} ! {ping, self()}
这时候的参数{registered_name,node_name}

修改后

-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()]).

进程也可以不适用pid,而是使用注册的名字+节点

完整例子

Messager允许客户端链接到集中的服务器并表明身份,用户不需要知道另一个用户所在Erlang节点的名称就可以发送消息
messager.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.

需要做两步:

  • 配置 server_node() 函数。
  • 将编译后的代码(messager.beam)拷贝到每一个你启动了 Erlang 的计算机上。

启动的节点:messager@super,c1@bilo,c2@kosken,c3@gollum
meesager@super启动服务器

(messenger@super)1> messenger:start_server().
true

c1@bilo\c2@kosken\c3@gollum上分别登录

(c1@bilbo)1> messenger:logon(peter).
true
logged_on

Peter向Fred发送消息

(c1@bilbo)2> messenger:message(fred, "hello").
ok
sent

如果有的离开了,出现下面的情况

(c2@kosken)2> messenger:message(fred, "peter doesn't like you").
ok
receiver_not_found

whereis(RegisteredName)检查是否注册了RegisteredName名称,如果存在返回pid,如果不存在返回原子值undefined
新的概念:server_transfer
server_transfer/4和server_transfer/5是不一样的函数
lists:keymemeber(Key,Position,Lists)函数遍历列表中的元组,查看每个元组的指定位置处的数据并判断元组该位置是否与 Key 相等
元组中的第一个元素的位置为 1,依次类推
如果发现某个Position处的key与元素相同,返回ture,否则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

http://wiki.jikexueyuan.com/project/erlang/example-two.html

posted @ 2017-03-29 11:36  zhangshihai1232  阅读(184)  评论(0)    收藏  举报