我的模块中有变量,并且有更新变量值的接收方法。并且多个进程同时调用此方法。当一个进程修改它时,我需要锁定这个变量。示例如下
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"}
匹配。 现在NewVal
和NextVal
绑定到"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"}