2015-12-04 40 views
0

我对Erlang很陌生,试图实现一个简单的class,它有一些方法来模拟数据库。 insert()只在流程图中插入key -> valueretrieve()仅从地图返回值。但是,我陷入了loop()。我究竟做错了什么?Erlang进程中的无限循环

-module(db). 
-export([start/0,stop/0,retrieve/1,insert/2]). 

start() -> 
    register(db, spawn(fun() -> 
       loop() 
      end) 
     ), 
    {started}. 


insert(Key, Value) -> 
    rpc({insert, Key, Value}). 

retrieve(Key) -> 
    rpc({retrieve, Key}). 

stop() -> 
    rpc({stop}). 

rpc(Request) -> 
    db ! {self(), Request}, 
    receive 
    {db, Reply} -> 
     Reply 
    end. 

loop() -> 
    receive 
    {rpc, {insert, Key, Value}} -> 
     put(Key, Value), 
     rpc ! {db, done}, 
     loop(); 
    {rpc, {retrieve, Key}} -> 
     Val = get(Key), 
     rpc ! {db, Val}, 
     loop(); 
    {rpc, {stop}} -> 
     exit(db,ok), 
     rpc ! {db, stopped}  
    end. 

所以,编译后:

我首先调用db:start(). 然后试图db:insert("A", 1).时,它就会被stucked。

谢谢

+2

第一个问题,在你走之前:Erlang *没有类或方法*。一切都是功能。每个函数都会返回一个值。虽然有[*进程*,而不是*类*](http://stackoverflow.com/questions/32294367/erlang-process-vs-java-thread/32296577#32296577),你可以发送*消息* ,但他们如何回应这些问题的方式与方法完全不同。 – zxq9

+0

谢谢。你是对的。我仍然不明白Erlang的一切工作原理 –

回答

5

的问题是loop/0功能。您使用rpc原子来模式匹配收到的消息({rpc, {insert, Key, Value}}),但是,正如您在rpc/1函数中所看到的那样,您始终会将格式为{self(), Request}的消息发送到db进程。

self()函数返回的格式<X.Y.Z>一个PID,这将永远比不上对原子rpc

例如,假设你想使用的功能insert/2插入一些数据和self()将返回PID <0.36.0> 。当rpc/1发送消息时,在线db ! {self(), {insert, Key, Value}},loop/0将收到{<0.36.0>, {insert, Key, Value}}消息,这将永远不会匹配{rpc, {insert, Key, Value}},因为rpc是一个原子。

的解决办法是改变rpc原子变量,像这样:

loop() -> 
receive 
{Rpc, {insert, Key, Value}} -> 
    put(Key, Value), 
    Rpc ! {db, done}, 
    loop(); 
{Rpc, {retrieve, Key}} -> 
    Val = get(Key), 
    Rpc ! {db, Val}, 
    loop(); 
{Rpc, {stop}} -> 
    Rpc ! {db, stopped}, 
    exit(whereis(db),ok) 

end. 

二郎变量以大写字母开头,这是不是rpc为什么我用Rpc

P.S:其实,你还有其他两个问题:

  1. loop/0,在那里你处理stop消息的最后一部分,你叫exit(db, ok)之前,你居然回答rpc。在这种情况下,你永远不会收到从db进程返回的{db, stopped}消息,该消息在那段时间已经死亡。这就是为什么我改变了订单,在Rpc ! {db, stopped}之后拨打exit/2
  2. 当您拨打exit/2时,您通过了作为第一个参数的db(原子),但exit/2函数期望将PID作为第一个参数,这会引起badarg错误。这就是为什么我将它更改为exit(whereis(db), ok)
+0

谢谢!就是这样 –

+1

不客气,@LuisLavieri。你应该阅读zxq9的答案,这是指编码并发erlang的一些重要问题。 为了简单起见,您还应该检查OTP。由于你是Erlang的新手,我建议你[学习一些Erlang书籍](http://learnyousomeerlang.com/)。恕我直言,这是最好的开始。 –

2

让我们仔细看看这一点。 “rpc”是什么意思? “远程过程调用” - 当然。但是在Erlang的一切是一个rpc,所以我们倾向于不使用这个术语。相反,我们区分同步消息(呼叫者阻止,等待响应)和异步消息(呼叫者只需触发消息并在世界中无需关心的情况下运行)。我们倾向于使用术语“呼叫”来表示同步消息,而使用“施放”表示异步消息。

我们可以很容易地编写,因为调用看起来很像上面的rpc,在Erlang中增加了一个独特的参考值来标记消息并监视我们发送消息的过程,以防万一它崩溃(所以我们没有得到左边挂,等待永远不会到来的回应......我们将在一点触摸你的代码):

% Synchronous handler 
call(Proc, Request) -> 
    Ref = monitor(process, Proc), 
    Proc ! {self(), Ref, Request}, 
    receive 
     {Ref, Res} -> 
      demonitor(Ref, [flush]), 
      Res; 
     {'DOWN', Ref, process, Proc, Reason} -> 
      {fail, Reason} 
    after 1000 -> 
     demonitor(Ref, [flush]), 
     {fail, timeout} 
    end. 

Cast是更容易一点:

cast(Proc, Message) -> 
    Proc ! Message, 
    ok. 

以上调用的定义意味着我们发送给的进程将接收一条形式为{SenderPID, Reference, Message}的消息。请注意,这是不同{sender, reference, message},因为小写字母值为atoms,这意味着它们是它们自己的值。

当我们receive消息我们是匹配关于接收到的消息的形状和值。这意味着,如果我有

receive 
    {number, X} -> 
     do_stuff(X) 
end 
在我的代码

,它不会匹配过程坐在那receive得到一个消息{blah, 25}。如果它收到另一个消息{number, 26}那么它将匹配,即receive将调用do_stuff/1并且该过程将继续。 (这两件事 - atomsVariables之间的区别与receive作品中的匹配方式 - 是您的代码挂起的原因。)初始邮件{blah, 25}仍将位于邮箱中,但位于队列的前端,所以下一个receive有机会匹配它。邮箱的这个属性有时非常有用。

但是,什么是一个全部看起来像?

高于你期望三种消息:

  • {insert, Key, Value}
  • {retrieve, Key}
  • stop

你穿起来有所不同,但是这是你想什么营业结束去做。通过我在上面写到的call/2函数运行插入消息将会看起来像这样:{From, Ref, {insert, Key, Value}}。所以,如果我们期望从进程的接收循环中得到任何响应,我们需要按照确切的形式进行匹配。我们如何捕捉意外消息或形成不良的消息?在receive条款的最后,我们可以把一个赤裸裸的变量,以匹配任何其他

loop(State) -> 
    receive 
     {From, Ref, {insert, Key, Value}} -> 
      NewState = insert(Key, Value, State), 
      From ! {Ref, ok}, 
      loop(NewState); 
     {From, Ref, {retrieve, Key}} -> 
      Value = retrieve(Key, State), 
      From ! {Ref, {ok, Value}}, 
      loop(State); 
     {From, Ref, stop} -> 
      ok = io:format("~tp: ~tp told me to stop!~n", [self(), From]), 
      From ! {Ref, shutting_down}, 
      exit(normal) 
     Unexpected -> 
      ok = io:format("~tp: Received unexpected message: ~tp~n", 
          [self(), Unexpected]), 
      loop(State) 
    end. 

你会发现,我使用进程字典。不要使用过程字典。这不是它的目的。你会覆盖重要的东西。或者丢掉重要的东西。或者......呃,不要这样做。使用字典或映射或gb_tree或其他代替,并将其作为过程'State变量传递。一旦你稍后开始编写OTP代码,这对你来说将变得非常自然。

玩弄这些东西,你很快就会高兴地发送垃圾邮件到你的程序死亡。

+0

感谢您的详细解释。我绝对尝试这一点。 –