Erlang进程存储状态

单一原则

单个赋值意味着一旦将值赋给变量,就不能将其他值赋给同一个变量。

1
2
3
4
1> X = 1.
1
2> X = 2.
** exception error: no match of right hand side value 2

在第一步中,Erlang尝试模式匹配X1。因为X是未绑定的,它将绑定到该值1。从现在开始,当模式匹配运算符X的左侧是时=,将发生正常的模式匹配。这就是为什么我们在第二步中出现错误的原因 - Erlang尝试匹配X,其值为12 因此导致no match错误。X只要1右侧有相应的,仍然可以匹配,如下所示。

1
2
3
4
3> X = 1.
1
4> {X, 2} = {1, 2}.
{1,2}

单一赋值在Erlang中是一个巨大的好处,因为它减少了函数调用产生 副作用的可能性。

递归、state

通过将状态从初始函数调用传递到下一个等来获得使用递归的部分方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-module(loop_counter).
-export([go/0]).

go() ->
go(0).

go(N) ->
io:format("N is ~p~n", [N]),
timer:sleep(1000),
go(N + 1).

%%% Erlang shell
1> c(loop_counter).
{ok,loop_counter}
2> loop_counter:go().
N is 0
N is 1
N is 2
N is 3

在上面例子中,使用函数的输入参数Ngo/1跟踪状态,并通过递归调用与N + 1下一个调用的参数相同的函数来递增它。能够增加值的事实意味着正在跟踪计数器的状态。它在这一点上并不是很有用,因为递归调用循环完全接管了当前的erlang进程。

可以使用erlang:spawn / 1 来打破我们的反向循环,远离主进程。

1
2
3
4
5
6
7
8
9
10
11
1> c(loop_counter).
{ok,loop_counter}
2> Pid = spawn(fun loop_counter:go/0).
N is 0
<0.39.0>
N is 1
N is 2
N is 3
N is 4
3> q().
ok

使用消息控制并查询状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-module(counter).
-export([new/0, click/1]).


new() ->
spawn(fun() -> loop(0) end).

loop(N) ->
receive
{click, From} ->
From ! N + 1,
loop(N + 1)
end.

click(Pid) ->
Pid ! {click, self()},
receive V -> V end.

%%% Erlang shell
1> c(counter).
{ok,counter}
2> C = counter:new().
<0.39.0>
3> counter:click(C).
1
4> counter:click(C).
2
5> counter:click(C).
3

当我调用时counter:new/0,该函数会生成一个初始计数器值为零的新递归循环,并返回生成进程的pid。循环函数立即进入一个receive块,在那里它无限期地等待来自任何进程的消息。我们将其设置为仅侦听一条消息:{click, From}期望From是调用进程的pid。

1
2
3
4
5
6
7
8
1> c(counter).
{ok,counter}
2> C = counter:new().
<0.39.0>
3> C ! {click, self()}.
{click,<0.32.0>}
4> receive V -> V end.
1

在这每次想要递增计数器时,不希望必须编写消息调用和接收块。这也是不安全的,因为它留下了很大的错误空间。因此,我们引入了一个API方法counter:click/1,该方法采用计数器pid并且知道发送和接收单击消息的正确方法。self/0counter:click/1调用中使用自动获取_调用_进程的pid ,这个是有效的,因为counter:click/1正在_由_调用进程进行评估。如果我们self/0在 counter:loop/1函数中调用它,它将返回正在评估循环的生成进程的值。

通过生成多个进程可以轻松创建和控制多个计数器。此外,API隐藏了大部分底层实现细节,可以使用实例,而不必知道它们的值实际上是一个进程标识符。

1
2
3
4
5
6
7
8
9
10
11
12
1> c(counter).
{ok,counter}
2> C1 = counter:new().
<0.39.0>
3> C2 = counter:new().
<0.41.0>
4> counter:click(C1).
1
5> counter:click(C1).
2
6> counter:click(C2).
1

可以修改循环函数以监听set消息,以允许手动设置计数器的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
loop(N) ->
receive
{click, From} ->
From ! N + 1,
loop(N + 1);
{set, Value, From} ->
From ! ok,
loop(Value)
end.

set(Pid, Value) ->
Pid ! {set, self(), Value},
receive V -> V end.