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热加载的实现原理:
- erlang的热更新由code_server.erl模块管理,采用一个private ets table管理代码,每个模块保留两个不能状态的代码:old 和 current
- 代码状态的切换:
- 当模块M第一次加载时,状态为current
- 修改M,编译并重新加载后,之前的current代码状态转变为old,新加载的代码状态为current。以后第一次调用该模块的代码将采用current状态的代码,old状态的代码还会被之前的进程通过local call 所调用
- 当再一次修改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).