Erlang--热更新

  热更新是erlang的一个重要特性:当程序调用M:F(A),总是调用的M:F的最新的编译过的载入的版本。

  • 一个简单的例子如下:
 1 -module(area_server).
 2 %%%================================EXPORT================================
 3 -export([rpc/2, loop/0, start/0, area/2]).
 4 
 5 
 6 %%%============================EXPORT FUNC================================
 7 start() ->
 8     spawn(area_server, loop, []).
 9 
10 area(Pid, Request) ->
11     rpc(Pid, Request).
12 
13 rpc(Pid, Request) ->
14     Pid ! {self(), Request},
15     receive
16         Response ->
17             Response
18     end.
19 
20 loop() ->
21     receive
22         {From, {rectangle, Width, Height}} ->
23             From ! Width * Height,
24             loop();
25         {From, {circlr, R}} ->
26             From ! 3.14 * R * R,
27             loop()
28     end.

  1.编译area_server模块(不是在shell使用c()函数,工程中采用erl -make命令和emakefile进行编译,后续将会增加一遍博文说明工程),然后启动工程,在shell里面分别执行命令及结果如下:

1 (test_erlang@WIN-12F3B5SEKIH)1> Pid = area_server:start().
2 <0.39.0>
3 (test_erlang@WIN-12F3B5SEKIH)2> area_server:area(Pid, {rectangle, 1, 1}).
4 1

  2.然后修改area_server模块,在area_server:rpc中添加一句打印:

1 %% TODO delete
2     io:format("~n------------------~p:~p-----------------~n", [?FILE, ?LINE]),
3     io:format("rpc = ~p~n", [rpc]),
4     io:format("-----------------------------------------------------------------~n"),

  3.重新编译后,在shell中执行area_server:area(Pid, {rectangle, 1, 1}).,结果如下:

1 (test_erlang@WIN-12F3B5SEKIH)3> area_server:area(Pid, {rectangle, 1, 1}).
2 1

  4.发现没有新增的打印,原因:编译新的模块后需要再主动加载,使用code:load_file命令后如下:

1 (test_erlang@WIN-12F3B5SEKIH)4> code:load_file(area_server). 
2 {module,area_server}
3 (test_erlang@WIN-12F3B5SEKIH)5> area_server:area(Pid, {rectangle, 1, 1}).
4 
5 ------------------"src/area_server.erl":29-----------------
6 rpc = rpc
7 -----------------------------------------------------------------
8 1

  主动加载后才会调用编译的新的模块。

  • erlang热加载的实现原理:
  1. erlang的热更新由code_server.erl模块管理,采用一个private ets table管理代码,每个模块保留两个不能状态的代码:old 和 current
  2. 代码状态的切换:
    1. 当模块M第一次加载时,状态为current
    2. 修改M,编译并重新加载后,之前的current代码状态转变为old,新加载的代码状态为current。以后第一次调用该模块的代码将采用current状态的代码,old状态的代码还会被之前的进程通过local call 所调用
    3. 当再一次修改M,编译并重新加载后,同样的current的状态的代码变为old,而之前的old代码将被干掉,从而使用之前的old状态的进程会被kill

      接着上面的例子看:把打印去掉,重新编译加载,shell中代码如下:

1 (test_erlang@WIN-12F3B5SEKIH)6> code:load_file(area_server).             
2 {error,not_purged}
3 (test_erlang@WIN-12F3B5SEKIH)7> 
4 =ERROR REPORT==== 10-Jan-2016::01:00:53 ===
5 Loading of f:/code_play/test_erlang/code/test_code/.ebin/area_server.beam failed: not_purged

      直接使用code:load_file(area_server)会出现错误,因为已经存在装备为old的代码,可以先使用code:purge()清除old状态代码,然后使用code:load_file(area_server),或者直接使用l(area_server),因为l(M)的实现就是上面两句:

1 %% l(Mod)
2 %%  Reload module Mod from file of same name
3 -spec l(Module) -> code:load_ret() when
4       Module :: module().
5 
6 l(Mod) ->
7     code:purge(Mod),
8     code:load_file(Mod).

      在shell中执行l(area_server),再调用area_server:start(Pid, {rectangle, 1, 1}),会出现shell“死机”的情况:

1 (test_erlang@WIN-12F3B5SEKIH)8> l(area_server).
2 {module,area_server}
3 (test_erlang@WIN-12F3B5SEKIH)9> area_server:area(Pid, {rectangle, 1, 1}).

                 shell中执行到第3行后,shell就没有反应了,这是因为:Pid这个进程已经被kill了,area函数一直阻塞在receive处。(tip:这种情况可以通过ctrl+G进入user switch command,利用s命令重启一个新的shell).

原文地址:https://www.cnblogs.com/joh-n-zhang/p/5117010.html