多个进程如何在Erlang中同时使用一个公共列表?

时间:2016-07-24 17:29:34

标签: concurrency erlang

我理解Erlang完全是关于并发性的,我们使用spawn/spawn_link来创建一个我不理解的过程所有进程如何同时使用一个公共用户列表?说出ordict / dict存储。

我想做的是;
1.产生的用户流程订阅/收听注册流程A

2.已注册流程A存储所有在线用户的{Pid, Userid}

3.当某个用户发送消息时,用户的流程会询问进程A是否有联机收件人。

erlang中发送消息是异步的,但当用户被多个用户发送消息时,它是否也是异步的?

2 个答案:

答案 0 :(得分:4)

您可以将进程A设为gen_server process,并将存储在线用户的任何数据结构保留为进程状态。可以使用gen_server:cast/2来存储新用户或删除新用户,并且可以使用gen_server:call/2检查用户是否在线。或者,您可以gen_server创建一个公开可读的ets table,以允许任何进程读取它以检查在线用户,但存储和删除仍需要转换为gen_server。您甚至可以使表可公开读写,以便任何进程都可以存储,删除或检查用户。但请记住,当创建它的进程死亡时,默认情况下会销毁ets表,因此即使创建它的gen_server死亡,您仍需要它保持不变,您必须安排它由其他一些过程继承,或give it to a supervisor

答案 1 :(得分:2)

严肃的解决方案应该使用Steve建议的OTP行为(gen_server,supervisor ......)。 无论如何,我写了一个实现服务器和客户端的小示例模块,可以使用命令erl -sname test在一个节点上启动(或使用erl -sname node1erl -sname node2的几个节点。 。)。

它还包含一个shell会话示例,它说明了大多数情况,我希望它可以帮助您跟踪进程之间的交换,同步或异步。

注意: 对用户列表的访问不是并发的,如果列表由服务器进程拥有,则不可能像在此示例中那样。这就是为什么史蒂夫建议使用ETS来存储信息并进行真正的并发访问。我试图编写带有接口的示例,这些接口应该允许使用ETS而不是元组列表进行快速重构。

-module(example).

-export([server/0,server_stop/1,server_register_name/2,server_get_address/2, server_quit/2, % server process and its interfaces
         client/1,quit/1,register_name/2,get_address/2,send_message/3,print_messages/1, % client process and its interfaces
         trace/0]). % to call the tracer for a nice message view

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Client interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

client(Node) ->
    % connect the current node to the servernode given in parameter
    % it will fail if the connection cannot be established
    true = net_kernel:connect_node(Node),
    % spawn a client process
    spawn(fun () -> client([],unregistered,{server,Node}) end).

register_name(ClientPid,Name) ->
    % use a helper to facilitate the trace of everything
    send_trace(ClientPid,{register_name,self(),Name}),
    % wait for an answer, it is then a synchronous call
    receive
        % no work needed, simply return any value
        M -> M 
    after 1000 ->
        % this introduce a timeout, if no answer is received after 1 second, consider it has failed
        no_answer_from_client
    end.

get_address(ClientPid,UserName) ->
    send_trace(ClientPid,{get_address,self(),UserName}),
    % wait for an answer, it is then a synchronous call
    receive
        % in this case, if the answer is tagged with ok, extract the Value (will be a Pid)
        {ok,Value} -> Value;
        M -> M 
    after 1000 ->
        no_answer_from_client
    end.

send_message(ClientPid,To,Message) ->
    % simply send the message, it is asynchronous
    send_trace(ClientPid,{send_message,To,Message}).

print_messages(ClientPid) ->
    send_trace(ClientPid,print_messages).

quit(ClientPid) ->
    send_trace(ClientPid,quit).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% client local functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

client(Messages,Name,Server) ->
    receive
        {register_name,From,UserName} when Name == unregistered ->
            % if not yet registered send the request to the server and
            % backward the answer to the requester
            Answer = server_register_name(Server,UserName),
            send_trace(From,Answer),
            NName = case Answer of
                registered -> UserName;
                _ -> Name
            end,
            client(Messages,NName,Server);
        {register_name,From,_} ->
            % if already registered reject the request
            send_trace(From,{already_registered_as,Name}),
            client(Messages,Name,Server);
        {get_address,From,UserName} when Name =/= unregistered ->
            Answer = server_get_address(Server,UserName),
            send_trace(From,Answer),
            client(Messages,Name,Server);           
        {send_message,To,Message} ->
            % directly send the message to the user, the server is not concerned
            send_trace(To,{new_message,{erlang:date(),erlang:time(),Name,Message}}),
            client(Messages,Name,Server);
        print_messages ->
            % print all mesages and empty the queue
            do_print_messages(Messages),
            client([],Name,Server);
        quit ->
            server_quit(Server,Name);
        {new_message,M} ->
            % append the new message
            client([M|Messages],Name,Server);
        _ ->
            client(Messages,Name,Server)
        end.

do_print_messages(Messages) ->
    lists:foreach(fun({D,T,W,M}) -> io:format("from ~p, at ~p on ~p, received ~p~n",[W,T,D,M]) end,Messages).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Server interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

server() ->
    true = register(server,spawn(fun () -> server([]) end)),
    node().

server_stop(Server) ->
    send_trace(Server,stop).

server_register_name(Server,User) ->
    send_trace(Server,{register_name,self(),User}),
    receive
        M -> M
    after 900 ->
        no_answer_from_server
    end.

server_get_address(Server,User) ->
    send_trace(Server,{get_address,self(),User}),
    receive
        M -> M
    after 900 ->
        no_answer_from_server
    end.

server_quit(Server,Name) ->
    send_trace(Server,{quit,Name}).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% server local functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

server(Users) ->
    receive
        stop ->
            ok;
        {register_name,From,User} ->
            case lists:keyfind(User,1,Users) of
                false ->
                    send_trace(From,registered),
                    server([{User,From}|Users]);
                _ -> 
                    send_trace(From,{already_exist,User}),
                    server(Users)
                end;
        {get_address,From,User} ->
            case lists:keyfind(User,1,Users) of
                false ->
                    send_trace(From,{does_not_exist,User}),
                    server(Users);
                {User,Pid} ->
                    send_trace(From,{ok,Pid}),
                    server(Users)
                end;
        {quit,Name} ->
            server(lists:keydelete(Name,1,Users))
    end.



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% global
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

trace() -> 
% start a collector, a viewer and trace the "trace_me" ...
    et_viewer:start([{trace_global, true}, {trace_pattern, {et,max}},{max_actors,20}]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% helpers
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

send_trace(To,Message) ->
    % all messages will be traced by "et"
    et:trace_me(50,self(),To,Message,[]),
    To ! Message.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% shell commands
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% c(example).
% example:trace().
% N = node().
% C1 = example:client(N).
% example:register_name(pid(0,5555,0),"fails").
% example:register_name(C1,"fails_again").
% example:server().
% example:register_name(C1,"Joe").
% C2 = example:client(N).
% example:register_name(C2,"Bob").
% example:print_messages(C1).
% C2 = example:get_address(C1,"Bob").
% example:send_message(C1,C2,"Hi Bob!").
% example:send_message(C1,C2,"Hi Bob! are you there?").
% example:print_messages(C2).
% example:send_message(C2,C1,"Hi Joe! Got your message.").
% example:print_messages(C2).
% example:print_messages(C1).
% example:quit(C1).
% example:get_address(C2,"Joe").
% example:server_stop({server,N}).
% example:get_address(C2,"Joe").
% example:get_address(C1,"Bob").

这里是事件查看器的摘录:

enter image description here