我对Erlang
很新,并试图实现一个简单的class
,它有一些模拟数据库的方法。 insert()
只需在流程图中插入key -> value
,retrieve()
只会返回地图中的值。但是,我陷入了loop()
。我做错了什么?
-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).
时,它会被卡住。
谢谢
答案 0 :(得分:4)
问题出在loop/0
函数中。您正在使用rpc
atom来模式匹配收到的消息({rpc, {insert, Key, Value}}
),但正如您在rpc/1
函数中看到的那样,您始终会发送格式为{{1}的消息数据库进程。
{self(), Request}
函数返回格式self()
的PID,它永远不会与原子<X.Y.Z>
匹配
例如,我们假设您尝试使用函数rpc
插入一些数据,而insert/2
会返回PID self()
。当<0.36.0>
发送消息时,在rpc/1
行,db ! {self(), {insert, Key, Value}}
会收到loop/0
消息,该消息永远不会与{<0.36.0>, {insert, Key, Value}}
匹配,因为{rpc, {insert, Key, Value}}
是一个原子。
解决方案是将rpc
atom更改为变量,如下所示:
rpc
Erlang变量以大写字母开头,这就是我使用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
的原因。
P.S。:实际上,您还有另外两个问题:
rpc
消息的loop/0
的最后部分,您在实际回答stop
之前致电exit(db, ok)
。在这种情况下,您永远不会收到来自rpc
进程的{db, stopped}
消息,该消息将在那时停止。这就是为什么我更改了订单,在db
之后发出exit/2
来电。Rpc ! {db, stopped}
时,你传递exit/2
这是一个原子,作为第一个参数,但是db
函数需要一个PID作为第一个参数,这会引发一个{ {1}}错误。这就是为什么我将其更改为exit/2
。答案 1 :(得分:1)
让我们更仔细地研究一下。你是什么意思&#34; rpc&#34;? &#34;远程程序调用&#34; - 当然。但是Erlang中的一切是一个rpc,所以我们倾向于不使用该术语。相反,我们区分同步消息(调用者阻塞,等待响应)和异步消息(调用者只是触发消息并在世界上无需小心地运行)。我们倾向于使用术语&#34; call&#34;对于同步消息和&#34;演员&#34;对于异步消息。
我们可以很容易地写出来,因为一个调用看起来很像你的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}
形式的消息。请注意,与<{em}不同,因为小写值为atoms,这意味着它们是自己的值。
当我们{sender, reference, message}
消息时,我们匹配收到的消息的形状和值。这意味着如果我有
receive
在我的代码和receive
{number, X} ->
do_stuff(X)
end
中的进程收到消息receive
将与不匹配。如果它收到另一个消息{blah, 25}
,那么它将匹配,{number, 26}
将调用receive
,此过程将继续。 (这两件事 - do_stuff/1
和atoms
之间的差异以及Variables
中的匹配方式 - 是您的代码挂起的原因。)初始消息receive
但是,仍然会在邮箱的前面位于邮箱中,因此下一个{blah, 25}
有机会匹配它。邮箱的这个属性有时非常有用。
但是一切都是什么样的?
上面你期待三种消息:
receive
{insert, Key, Value}
{retrieve, Key}
你用不同的方式装扮它们,但这是你想要做的事情的结束。通过我上面写的stop
函数运行插入消息,它看起来像这样:call/2
。因此,如果我们期望来自流程接收循环的任何响应,我们将需要匹配该确切的表单。我们如何捕获意外消息或错误形成的消息?在{From, Ref, {insert, Key, Value}}
子句的末尾,我们可以在任何上放置一个裸变量来匹配:
receive
您会注意到我使用流程词典 。不要使用过程字典。这不是它的用途。你会覆盖一些重要的东西。或者放下重要的东西。或者...... ......,不要这样做。使用字典或地图或gb_tree或其他任何东西,然后将其传递给过程&#39; 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.
变量。一旦你以后开始编写OTP代码,这将成为一件非常自然的事情。
玩弄这些东西,你很快就会很高兴地将你的流程发送到死亡。