[Erlang10]为什么热更新时,Shell执行2次l(Module)后会把原来用到Module的进程 kill?
0. 问题引入:
-module(hot_code_server).
-compile(export_all).
start() –>
erlang:register(?MODULE,
erlang:spawn_link(fun() –>
loop()
end)). loop() –>
receive
stop –>
io:format("stop~n");
Msg –>
io:format("Recv:~p~n",[Msg]),
loop()
end.
在shell中执行:
101> hot_code_server:start().
true
102> whereis(hot_code_server).
<0.191.0>
103> l(hot_code_server).
{module,hot_code_server}
104> l(hot_code_server).
** exception exit: killed
试想一下,如果你的进程没有放在监控监控树(supervisor)下,那么在2次热更新后就直接被kill掉,多么可怕。。。。
为了清楚这个问题,先来看看Erlang的代码更新原理:
1. Erlang热更新:
Erlang的热更新是用code_sever.erl模块来管理的,code_server基于VM进程,用一个private Ets Table来管理代码,同一个模块最多可以在code_server里面有2个不同的版本,运行里可以随意切换这2个不同的版本,当你使用c(Module)来编译Module时,新版本会自动的加载
【调用code: purge(Module),code: load_file(Module).】。
Tip: 必须了解的概念:
Erlang函数有local call和external call的区别,Local call 就是在函数在被定义的模块里面被调用,可以直接被调用: Func(Args); External call 就是显式的使用Module:Func(Args)来调用或import别的模块进来的调用.
当同一个模块有2个版本被加载里,所有的local call都可以工作在当前版本状态,但是:external call只能调用到最新的版本!【所以你在模块里面使用Func(Args)和Moudle:Func(Args)还是有很大区别的!特别是在更新模块】
验证如下:
-define(version,1).
func_test(Name) –>
erlang:register(Name,erlang:spawn_link(fun() -> print(Name) end )). print(Name) –>
receive
Msg –>
io:format("Name:~p Msg: ~p Version:~p ~n",[Name,Msg,?version]),
?MODULE:print(Name)
end.
Shell中调用如下:
39> c(hot_code_server).
{ok,hot_code_server}
40> hot_code_server:func_test(test1).
true
41> erlang:send(test1,got).
Name:test1 Msg: got Version:
然后修改代码如下:
-define(version,).
Shell中再更新一次:【注意这时的test1进程还在运行中】
42> c(hot_code_server).
{ok,hot_code_server}
43> erlang:send(test1,got).
Name:test1 Msg: got1 Version:
got1
44> erlang:send(test1,got2).
Name:test1 Msg: got2 Version:2
got2
可以看到,当test1进程接收到第一个信息got1时,还是使用的old code,但这个消息进入新一轮尾递归时调用的是?MODULE: print(Name),调用new code,所以就在下一轮时变成了version:2啦。
那么把代码中的?MODULE: print(Name),改成print(Name)测试下 local call会怎么样?
-define(version,1).
func_test(Name) –>
erlang:register(Name,erlang:spawn_link(fun() -> print(Name) end )). print(Name) –>
receive
Msg –>
io:format("Name:~p Msg: ~p Version:~p ~n",[Name,Msg,?version]),
print(Name)
end.
Shell中重新更新一次:
47> c(hot_code_server).
{ok,hot_code_server}
48> hot_code_server:func_test(test2).
true
49> erlang:send(test2,got).
Name:test2 Msg: got Version:1
got
再次把:
-define(version,).
Shell中再更新一次:【注意这时的test2进程还在运行中】
50> c(hot_code_server).
{ok,hot_code_server}
51> erlang:send(test2,got).
Name:test2 Msg: got1 Version:1
got1
52> erlang:send(test1,got2).
Name:test2 Msg: got2 Version:1
got2
无论再对test2发多少信息,得到的version都是1,是old code,因为这个进程没有结束过,一直调用的old code,但是如果你现在新起进程test3,那么这个print(Name)调用的就是new code,也就是说是version 2啦。
结论:
1. 使用?MODULE:Func(Args)调用,永远调用的最新代码;
2. 使用Func(Args)调用的进程,并且进程已在运行中,那么就会一直使用原来的old code,无论是不是更新了new code;
3. 使用Func(Args)调用的进程,更新了new code后,再用这个函数新启动一个进程,那么这个新进程的code就是new code。
接下来:一起回到开始的问题:为什么2次l(Module)或c(Module)后会把运行状态还在调用这个Module中的进程kill掉呢?
2. l(Module)原理:
c.erl里面有它的实现:就是先清理旧版本,后加载(把当前进行的版本变成旧版本,新版本为最新改动的版本)。

为什么会被kill掉,你可以查看相关code: purge(Mod)的实现code_server: do_purge(Mod) :

3. 结论:
1. l(Mod)一次时,把运行中的代码变成old code【Version1】,最新改动的Mod变成new code【Version2】,
2. l(Mod)第二次时,把【Version1】清理,并把所有用到Version1的进程kill,把Version2变成old code,并把最新改动【Version3】更新为new code.
3. 以上就是在Shell中更新时,会把进程给kill的原因啦。
4. 题外话:
4.1 Erlang三种装载模块的方法:
1) 直接调用模块中的函数,code server进程将会搜索相应的beam文件,然后装载之;
2) 编译该模块(相应的函数是compile:file(Module))后会自动装载编译好的模块,在shell中是通过命令c(Module);
3) 显式的装载模块,通过调用code:load_file(Module)转载指定的模块,在本机上可以通过命令l(Module)装载模块,在网络中可以通过命令nl(Module)将模块装载到各个节点上。
4.2 怎么判定一个函数是local call 还是 exteral call:
%%使用erlc -S opcode_test.erl 生成opcode_test.S文件
-module(opcode_test).
-export([test/0]).
-import(hello, [hello/0]).
test2() –>
ok.
test() –>
test2(),
?MODULE:test2(),
hello(),
hello:hello(),
ok.
生成的opcode_test.S文件部分如下:
{function, test, 0, 4}.
{label,3}.
{line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
19}]}.
{func_info,{atom,opcode_test},{atom,test},0}.
{label,4}.
{allocate,0,0}.
{line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
20}]}.
{call,0,{f,2}}. %%这是local call: test2(),
{line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
21}]}.
{call_ext,0,{extfunc,opcode_test,test2,0}}. %%这是exteral call: ?MODULE:test2().
{line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
22}]}.
{call_ext,0,{extfunc,hello,hello,0}}.
{line,[{location,"c:/Users/admin/Documents/GitHub/erl_excrise/src/opcode_test.erl",
23}]}.
{call_ext,0,{extfunc,hello,hello,0}}.
{move,{atom,ok},{x,0}}.
{deallocate,0}.
return.
相关查阅文章:
1. Learn Some Erlang: http://learnyousomeerlang.com/designing-a-concurrent-application#hot-code-loving
2. Erlang语法对应的opcode: http://mryufeng.iteye.com/blog/472840
以上说的都是指Module级别的热更新,如果你想了解产品级别的请看【还没用到过……】
Release Handle : http://www.erlang.org/doc/design_principles/release_handling.html
[Erlang10]为什么热更新时,Shell执行2次l(Module)后会把原来用到Module的进程 kill?的更多相关文章
- Lua热更新时正确设置文件名
Lua热更新时正确设置文件名(金庆的专栏 2016.12)Lua热更新模块见:https://github.com/jinq0123/hotfix其中使用 load(chunk) 来加载更新后的内容, ...
- 登录linux时 shell执行顺序
# .bash_history,.bash_logout,.bash_profile,.bashrc/etc/profile 全局.bash_history 记录当前登录用户历史操作的命令.bash_ ...
- 使用bugly热更新时自定义升级弹窗的UI样式
项目的热更新用的bugly,不过一直都只是使用他自带的升级弹窗. 不过UI小姐姐说弹窗太丑了,要自定义. bugly有提供自定义UI的官方文档:https://bugly.qq.com/docs/us ...
- 安装phoenix时,执行命令./sqlline.py hostname1,hostname2.hostname3..... 时报错 ImportError: No module named argparse
问题描述: 怎么解决呢: 网上看了好多方法,但是本屌丝表示看不懂啊,没理解人家的博客的博大精深,好吧我们回到正题!! 先切换到root用户下安装这个东西 yum install python-a ...
- 【Quick 3.3】资源脚本加密及热更新(三)热更新模块
[Quick 3.3]资源脚本加密及热更新(三)热更新模块 注:本文基于Quick-cocos2dx-3.3版本编写 一.介绍 lua相对于c++开发的优点之一是代码可以在运行的时候才加载,基于此我们 ...
- ios app 实现热更新(无需发新版本实现app添加新功能)
目前能够实现热更新的方法,总结起来有以下三种 1. 使用FaceBook 的开源框架 reactive native,使用js写原生的iOS应用 ios app可以在运行时从服务器拉取最新的js文件到 ...
- (转载)李剑英的CSLight入门指南结合NGUI热更新
原地址:http://www.xuanyusong.com/archives/3075 李剑英的CSLight入门指南文档撰写者:GraphicQQ: 1065147807 一. CSLIGHT 作者 ...
- 搭建带热更新功能的本地开发node server
引言 使用webpack有一段时间了,对其中的热更新的大概理解是:对某个模块做了修改,页面只做局部更新而不需要刷新整个页面来进行更新.这样就能节省因为整个页面刷新所产生开销的时间,模块热加载加快了开发 ...
- 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- 使用FairyGUI (二)
上次讲解了FairyGUI的最简单的热更新办法,并对其中一个Demo进行了修改并做成了热更新的方式. 这次我们来一个更加复杂一些的情况:Emoji. FairyGUI的 Example 04 - ...
随机推荐
- Django-2的路由层(URLconf)
URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL与要为该URL调用的视图函数之间的映射表:你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码 ...
- rabbitMQ消息队列1
rabbitmq 消息队列 解耦 异步 优点:解决排队问题 缺点: 不能保证任务被及时的执行 应用场景:去哪儿, 同步 优点:保证任务被及时的执行 缺点:排队问题 大并发 Web nginx 1000 ...
- 比XGBOOST更快--LightGBM介绍
xgboost的出现,让数据民工们告别了传统的机器学习算法们:RF.GBM.SVM.LASSO.........现在,微软推出了一个新的boosting框架,想要挑战xgboost的江湖地位.笔者尝试 ...
- Netty简单的重连机制
其实重连机制并不是多么多高深的技术,其实就是一个在客户端做一个简单的判断,如果连接断了,那么就重新调用连接服务端的代码 当然,我们重连的动作肯定是发生在断连之后发生的,我们可以在上篇的心跳机制的基础上 ...
- python算法之归并排序
归并排序 归并排序是采用分治法的一个非常典型的应用.归并排序的思想就是先递归分解数组,再合并数组. 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相 ...
- MVC地址输出变为小写
其实个人不知道有什么区别,看到大多数都是小写,还是小写吧 去nuget安装一个第三方,然后对路由简单修改一下 插件名称:LowercaseRoutesMVC 方法名:MapRouteLowercase ...
- Python中的列表生成式和多层表达式
Python中的列表生成式和多层表达式 如何生成[1x1, 2x2, 3x3, ..., 10x10]的列表? L=[]; ,): L.append(x*x) print L print (" ...
- Unity 输入与控制
1. 鼠标输入 有关的方法和变量如下: 在 Unity 中,鼠标位置用屏幕的像素坐标表示,屏幕左下角为(0,0),右上角为(Screen.width,Screen.height). 2. 键盘操作 有 ...
- UITextView设置占位文字
这里只介绍一种,这种方便扩展,可以占位文字颜色. 我们继承一个UITextView: #import <UIKit/UIKit.h> @interface MyTextView : UIT ...
- ansible的ad-hoc模式
一.什么是ad-hoc模式 ansible中有两种模式,分别是ad-hoc模式和playbook模式 ad-hoc简而言之,就是"临时命令" 二.ad-hoc模式使用的场景 场景一 ...