有没有办法在 Erlang 中的不同进程之间锁定变量



我的模块中有变量,并且有更新变量值的接收方法。并且多个进程同时调用此方法。当一个进程修改它时,我需要锁定这个变量。示例如下

mytest.erl

%%%-------------------------------------------------------------------
-module(mytest).
%% API
-export([start_link/0,display/1,callDisplay/2]).
start_link()->
Pid=spawn(mytest,display,["Hello"]),
Pid.
display(Val) ->
io:format("It started: ~p",[Val]),
NextVal=
receive
{call,Msg}->
NewVal=Val++" "++Msg++" ",
NewVal;
stop->
true
end,
display(NextVal).
callDisplay(Pid,Val)->
Pid!{call,Val}.

启动它

Pid=mytest:start_link().

两个进程同时调用它

P1=spawn(mytest,callDisplay,[Pid,"Walter"]),
P2=spawn(mytest,callDisplay,[Pid,"Dave"]).
我希望它可以像"你好沃尔特戴夫">

一样一个接一个地添加"沃尔特","戴夫",但是,当它们一起运行太多时,一些名字(沃尔特、戴夫等)将被覆盖。

因为当P1、P2同时启动时,Val都是"你好"。P1 添加"Walter"变为"Hello Walter",P2 添加"Dave"变为"Hello Dave"。P1首先将其保存为"Hello Walter",然后P2将其保存为"Hello Dave",因此结果将是"Hello Dave"。"你好沃尔特"被"你好戴夫"取代,"沃尔特"永远失去了。

有什么方法可以锁定"瓦尔",所以当我们添加"瓦尔特"时,"戴夫"会等到值设置完成?

尽管这是一个古老的问题,但值得解释。 从你说的,如果我是对的, 你希望看到

"你好

沃尔特"和"你好戴夫"。但是,您会看到连续的名字被附加到前者之后,例如"你好沃尔特戴夫。

这种行为是正常的,为了看到这一点,让我们简要地看一下 Erlang 内存模型。Erlang进程内存分为三个主要部分:

进程控制块(PCB):它保存进程pid,注册名称,表,状态和指向其队列中的消息的指针。

叠:这将保存函数参数、局部变量和函数返回地址。

私有堆:保存传入的消息复合数据,如元组、列表和二进制(不大于 64 字节)。

这些内存中的所有数据都属于拥有进程,并且是私有的。

阶段 1:

调用Pid=spawn(mytest,display,["Hello"])时,将创建服务器进程,然后调用将"Hello"作为参数传递的显示函数。由于display/1是在服务进程中执行的,因此"Hello"参数位于服务器的进程堆栈中。display/1的执行将继续,直到它到达receive子句,然后阻止并等待与您的格式匹配的消息。

第 2 阶段:

现在P1启动,它执行ServerPid ! {call, "Walter"},然后P2执行ServerPid ! {call, "Dave"}。在这两种情况下,erlang 都会创建邮件的副本并将其发送到服务器的进程邮箱(专用堆)。邮箱中的此复制邮件属于服务器进程,而不是客户端的进程。 现在,当{call, "Walter"}匹配时,Msg绑定到"Walter"。 从阶段 1开始,我们知道Val被限制在"Hello"上,Newval然后被限制在
"Val ++ " " ++ Msg" = "Hello Walter"上。

此时,P2 的消息{call, "Dave"}仍在服务器的邮箱中等待下一个receive子句,该子句将在下一次递归调用display/1中发生。NextVal绑定到NewVal,并且递归调用dispaly/1"Hello Walter"在参数时传递。这给出了第一个打印"Hello Walter ",该现在也存在于服务器的进程堆栈中。

现在,当再次到达receive子句时,P2的消息{call, "Dave"}匹配。 现在NewValNextVal绑定到"Hello Walter" ++ " " ++ "Dave" = "Hello Walter Dave".这被作为参数传递给display/1作为打印Hello Walter Dave的新Val。简而言之,此变量在每个服务器循环中都会更新。它与gen_server行为中的State术语具有相同的目的。在您的情况下,连续的客户端调用只是将消息附加到此服务状态变量。现在回答你的问题,

有什么方法可以锁定Val,所以当我们添加"Walter"时,"Dave"会等到值设置完成?

不。不是通过锁定。Erlang 不是这样工作的。 没有进程锁定构造,
因为它不需要进程锁定构造。 数据(变量)对于创建它的进程始终是不可变和私有的(保留在共享堆中的大型二进制文件除外)。 此外,接收进程处理的不是您在Pid ! Msg构造中使用的实际消息。是它的副本。display/1函数中的Val参数是私有的,属于服务器进程,因为它位于堆栈内存中,因为每次调用display/1都是由服务器进程本身进行的。因此,任何其他进程都无法锁定甚至看不到该变量。

是的。通过顺序消息处理这正是服务器进程正在执行的操作。一次从队列中轮询一条消息。当{call, "Walter"}被带走时,{call, "Dave"}正在排队等候。您之所以看到意外问候语,是因为您更改了服务器状态,display/1参数用于下一个display/1调用哪个进程{call, "Dave"}

最新更新