Erlang Thursday - ETS介绍第二篇

今天的Erlang Thursday继续介绍 ets 模块以及ETS的概况。

上次我们看到ETS表在其父进程崩溃的时候被删除了,那么问题来了,我们怎么能够在其父进程崩溃的时候依然保持ETS表活着呢?

为解决这个问题,我们将研究函数 ets:give_away/3 以及在创建表的时候指定的参数 heir 。

首先我们创建一个函数,它将代表一个进程,而我们可以将表的所有权赋予它的。这个函数只是等待消息而且永远不超时。

1
2
Fun = fun() -> receive after infinity -> ok end end.
% #Fun<erl_eval.20.54118792>

现在我们创建一个运行该函数的进程。

1
2
Process = spawn(Fun).
% <0.53.0>

然后我们创建一个新的ETS表,

1
2
Table = ets:new(table, []).
% 20498

并且将它赋予我们刚刚创建的进程。

1
2
ets:give_away(Table, Process, []).
% true

我查看表信息可以看到表的所有者是我们创建的进程,因为这个进程的PID和表消息里的所有者元组的PID一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ets:info(Table).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,305},
% {owner,<0.53.0>},
% {heir,none},
% {name,table},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

现在我们已经进行了所谓的所有权转移,那么是时候将原来的所有者进程也就是我们当前的shell进程崩溃掉。

1
2
3
4
1 = 2.
% ** exception error: no match of right hand side value 2
self().
% <0.58.0>

我们检查我们创建的进程是否还活着,绝大多数情况下应该是活着的。

1
2
is_process_alive(Process).
% true

在检查表的信息,看看它是否还活着。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ets:info(Table).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,305},
% {owner,<0.53.0>},
% {heir,none},
% {name,table},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

它依然还活着!!!我们已经转移了所有权,所以如果我们自己的进程崩溃的话,ETS表依然是活着的。

是时候杀掉那个进程了。

1
2
3
4
exit(Process, "Because").
% true
is_process_alive(Process).
% false

然后表就消失了…

1
2
ets:info(Table).
% undefined

这一次,让我们在创建一个ETS表的时候用 heir 选项,来利用ETS表的所有权转移给一个继承人的魔法。

在这次场景里,当所有者进程死掉的时候shell将是继承人。

1
2
TableWithHeir = ets:new(table, [{heir, self(), "something went wrong"}]).
% 24594

我们创建一个新的进程,然后将ETS表的所有权赋予这个新的进程。

1
2
3
4
Process2 = spawn(Fun).
% <0.71.0>
ets:give_away(TableWithHeir, Process2, []).
% true

我们检查表的信息,我们可以看到表的所有者是新的进程,而它的继承人是我们当前的shell进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
self().
% <0.58.0>
ets:info(TableWithHeir).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,349},
% {owner,<0.71.0>},
% {heir,<0.58.0>},
% {name,table},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

现在再次杀掉所有者进程了……

1
2
3
4
exit(Process2, "Because").
% true
is_process_alive(Process2).
% false

我们在检查表的信息,我们可以看到当前的shell进程既是所有者又是继承人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ets:info(TableWithHeir).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,349},
% {owner,<0.58.0>},
% {heir,<0.58.0>},
% {name,table},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

我们创建一个新的进程,然后我们把表转移给它。

1
2
3
4
Process3 = spawn(Fun).
% <0.78.0>
ets:give_away(TableWithHeir, Process3, []).
% true

所有者变成了新的进程,我们当前的shell进程依然是继承人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ets:info(TableWithHeir).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,349},
% {owner,<0.78.0>},
% {heir,<0.58.0>},
% {name,table},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

通过利用指定继承人的能力,同时用 ets:give_away/3 函数,我们可以帮助ETS表长生不死。

一种可能利用的方式是,我们有一个监督进程,它创建一个“继承人”进程,然后创建一个子进程,这个子进程拥有ETS表,如果这个子进程死掉,它将转移所有权给继承人进程。直到新的所有者进程被重新创建,然后继承人进程便可以将ETS表的所有权转移给这个新创建的进程。

原文链接: https://www.proctor-it.com/erlang-thursday-ets-introduction-part-2/